@code-essentials/utils 1.0.1 → 1.0.3
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/dist/async-variable.d.ts +19 -0
- package/dist/async-variable.js +103 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +5 -0
- package/dist/lock.d.ts +16 -0
- package/dist/lock.js +39 -0
- package/dist/object.d.ts +10 -0
- package/dist/object.js +34 -0
- package/dist/observable-list.d.ts +24 -0
- package/dist/observable-list.js +75 -0
- package/dist/types.d.ts +12 -0
- package/dist/types.js +6 -0
- package/package.json +7 -3
- package/.vscode/launch.json +0 -21
- package/.vscode/tasks.json +0 -22
- package/src/async-variable.ts +0 -146
- package/src/index.ts +0 -5
- package/src/lock.spec.ts +0 -38
- package/src/lock.ts +0 -50
- package/src/object.spec.ts +0 -39
- package/src/object.ts +0 -60
- package/src/observable-list.spec.ts +0 -25
- package/src/observable-list.ts +0 -114
- package/src/types.ts +0 -21
- package/tsconfig.debug.json +0 -9
- package/tsconfig.json +0 -9
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export declare class AsyncVariable<T> implements PromiseLike<T> {
|
|
2
|
+
#private;
|
|
3
|
+
get value(): T;
|
|
4
|
+
set value(value: T);
|
|
5
|
+
get error(): any;
|
|
6
|
+
set error(error: any);
|
|
7
|
+
get complete(): boolean;
|
|
8
|
+
constructor();
|
|
9
|
+
init(): Promise<void>;
|
|
10
|
+
read(): Promise<T>;
|
|
11
|
+
then<TResult1 = T, TResult2 = never>(onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | null | undefined, onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null | undefined): PromiseLike<TResult1 | TResult2>;
|
|
12
|
+
set(value: T, throw_if_set?: boolean): Promise<void>;
|
|
13
|
+
reject(error: unknown, throw_if_set?: boolean): Promise<void>;
|
|
14
|
+
static performCallback<R = void>(fn: (cb: (err?: unknown, res?: R) => void) => void): AsyncVariable<R>;
|
|
15
|
+
perform(fn: () => Promise<T>): this;
|
|
16
|
+
timeout(milliseconds: number): this;
|
|
17
|
+
static perform<T>(fn: () => Promise<T>): AsyncVariable<T>;
|
|
18
|
+
static wait(milliseconds: number): AsyncVariable<void>;
|
|
19
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
export class AsyncVariable {
|
|
2
|
+
#result;
|
|
3
|
+
#result_res;
|
|
4
|
+
#result_p;
|
|
5
|
+
#initialization;
|
|
6
|
+
get value() {
|
|
7
|
+
if (!this.#result)
|
|
8
|
+
throw new Error('incomplete');
|
|
9
|
+
if (this.#result.type === "rejected")
|
|
10
|
+
throw this.#result.error;
|
|
11
|
+
return this.#result.value;
|
|
12
|
+
}
|
|
13
|
+
set value(value) {
|
|
14
|
+
this.set(value);
|
|
15
|
+
}
|
|
16
|
+
get error() {
|
|
17
|
+
if (!this.#result)
|
|
18
|
+
throw new Error('incomplete');
|
|
19
|
+
if (this.#result.type === "resolved")
|
|
20
|
+
throw new Error(this.#result.type);
|
|
21
|
+
return this.#result.error;
|
|
22
|
+
}
|
|
23
|
+
set error(error) {
|
|
24
|
+
this.reject(error);
|
|
25
|
+
}
|
|
26
|
+
get complete() {
|
|
27
|
+
return this.#result !== undefined;
|
|
28
|
+
}
|
|
29
|
+
constructor() {
|
|
30
|
+
this.#initialization = new Promise(initialized =>
|
|
31
|
+
// prevents unhandled rejection
|
|
32
|
+
this.#result_p = new Promise(res => {
|
|
33
|
+
this.#result_res = res;
|
|
34
|
+
initialized();
|
|
35
|
+
}));
|
|
36
|
+
}
|
|
37
|
+
async init() {
|
|
38
|
+
await this.#initialization;
|
|
39
|
+
}
|
|
40
|
+
async read() {
|
|
41
|
+
await this.#initialization;
|
|
42
|
+
await this.#result_p;
|
|
43
|
+
return this.value;
|
|
44
|
+
}
|
|
45
|
+
then(onfulfilled, onrejected) {
|
|
46
|
+
return this.read().then(onfulfilled, onrejected);
|
|
47
|
+
}
|
|
48
|
+
async #complete(throw_if_set = true) {
|
|
49
|
+
await this.#initialization;
|
|
50
|
+
if (this.complete) {
|
|
51
|
+
if (throw_if_set)
|
|
52
|
+
throw new Error('already set');
|
|
53
|
+
else
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
async set(value, throw_if_set = true) {
|
|
59
|
+
if (await this.#complete(throw_if_set)) {
|
|
60
|
+
this.#result_res(this.#result = {
|
|
61
|
+
type: "resolved",
|
|
62
|
+
value
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
async reject(error, throw_if_set = true) {
|
|
67
|
+
if (await this.#complete(throw_if_set)) {
|
|
68
|
+
this.#result_res(this.#result = {
|
|
69
|
+
type: "rejected",
|
|
70
|
+
error
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
static performCallback(fn) {
|
|
75
|
+
const av = new AsyncVariable();
|
|
76
|
+
fn(async (err, res) => {
|
|
77
|
+
if (err)
|
|
78
|
+
await av.error(err);
|
|
79
|
+
else
|
|
80
|
+
await av.set(res);
|
|
81
|
+
});
|
|
82
|
+
return av;
|
|
83
|
+
}
|
|
84
|
+
perform(fn) {
|
|
85
|
+
fn().then(value => this.set(value)).catch(error => this.reject(error));
|
|
86
|
+
return this;
|
|
87
|
+
}
|
|
88
|
+
timeout(milliseconds) {
|
|
89
|
+
AsyncVariable.wait(milliseconds).then(() => {
|
|
90
|
+
if (!this.complete)
|
|
91
|
+
this.reject("timeout");
|
|
92
|
+
});
|
|
93
|
+
return this;
|
|
94
|
+
}
|
|
95
|
+
static perform(fn) {
|
|
96
|
+
return new AsyncVariable().perform(fn);
|
|
97
|
+
}
|
|
98
|
+
static wait(milliseconds) {
|
|
99
|
+
const res = new AsyncVariable();
|
|
100
|
+
setTimeout(() => res.set(), milliseconds);
|
|
101
|
+
return res;
|
|
102
|
+
}
|
|
103
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
package/dist/lock.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export declare class Lock<Of = void> {
|
|
2
|
+
#private;
|
|
3
|
+
get isAcquired(): boolean;
|
|
4
|
+
acquire(of: Of): LockContext<Of>;
|
|
5
|
+
protected release(_context: LockContext<Of>): void;
|
|
6
|
+
static readonly ERR_ACQUIRED = "lock currently acquired";
|
|
7
|
+
}
|
|
8
|
+
export declare class LockContext<Of> implements Disposable {
|
|
9
|
+
#private;
|
|
10
|
+
readonly lock: Lock<Of>;
|
|
11
|
+
readonly of: Of;
|
|
12
|
+
constructor(lock: Lock<Of>, of: Of, release: (conext: LockContext<Of>) => void);
|
|
13
|
+
release(): void;
|
|
14
|
+
[Symbol.dispose](): void;
|
|
15
|
+
static readonly ERR_LOCK_RELEASED = "lock context already released";
|
|
16
|
+
}
|
package/dist/lock.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export class Lock {
|
|
2
|
+
#isAcquired = false;
|
|
3
|
+
get isAcquired() {
|
|
4
|
+
return this.#isAcquired;
|
|
5
|
+
}
|
|
6
|
+
acquire(of) {
|
|
7
|
+
if (this.#isAcquired)
|
|
8
|
+
throw new Error(Lock.ERR_ACQUIRED);
|
|
9
|
+
this.#isAcquired = true;
|
|
10
|
+
return new LockContext(this, of, this.release.bind(this));
|
|
11
|
+
}
|
|
12
|
+
release(_context) {
|
|
13
|
+
if (!this.isAcquired)
|
|
14
|
+
throw new Error();
|
|
15
|
+
this.#isAcquired = false;
|
|
16
|
+
}
|
|
17
|
+
static ERR_ACQUIRED = 'lock currently acquired';
|
|
18
|
+
}
|
|
19
|
+
export class LockContext {
|
|
20
|
+
lock;
|
|
21
|
+
of;
|
|
22
|
+
#release;
|
|
23
|
+
#isAcquired = true;
|
|
24
|
+
constructor(lock, of, release) {
|
|
25
|
+
this.lock = lock;
|
|
26
|
+
this.of = of;
|
|
27
|
+
this.#release = release;
|
|
28
|
+
}
|
|
29
|
+
release() {
|
|
30
|
+
if (!this.#isAcquired)
|
|
31
|
+
throw new Error(LockContext.ERR_LOCK_RELEASED);
|
|
32
|
+
this.#isAcquired = false;
|
|
33
|
+
this.#release(this);
|
|
34
|
+
}
|
|
35
|
+
[Symbol.dispose]() {
|
|
36
|
+
this.release();
|
|
37
|
+
}
|
|
38
|
+
static ERR_LOCK_RELEASED = "lock context already released";
|
|
39
|
+
}
|
package/dist/object.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export type Values<T extends object> = T[keyof T];
|
|
2
|
+
export type PropertyPath<T> = [] | T extends object ? Values<{
|
|
3
|
+
[K in keyof T]: [K] | [K, Values<PropertyPath<T[K]>>];
|
|
4
|
+
}> : [];
|
|
5
|
+
export type PropertyType<T, Property extends PropertyPath<T>> = PropertyType_<T, Property>;
|
|
6
|
+
type PropertyType_<T, Property extends any[], Constructed extends any[] = []> = (Property & Constructed) extends never ? Values<{
|
|
7
|
+
[K in keyof T as Property extends [...Constructed, K, ...any[]] ? K : never]: PropertyType_<T[K], Property, [...Constructed, K]>;
|
|
8
|
+
}> : T;
|
|
9
|
+
export declare function replaceProperty<T, Property extends PropertyPath<T> = PropertyPath<T>>(obj: T, property: Property, value: any): T;
|
|
10
|
+
export {};
|
package/dist/object.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// export type PropertyType<T, Property extends PropertyPath<T> & PropertyKey[]> = PropertyType_<T, Property>
|
|
2
|
+
// type PropertyType_<T, Property extends PropertyKey[], Constructed extends PropertyKey[] = []> =
|
|
3
|
+
// (Property & Constructed) extends never ?
|
|
4
|
+
// Values<{
|
|
5
|
+
// [K in keyof T as Property extends [...Constructed, K, ...PropertyKey[]] ? K : never]:
|
|
6
|
+
// PropertyType_<T[K], Property, [...Constructed, K]>
|
|
7
|
+
// }> :
|
|
8
|
+
// T
|
|
9
|
+
// type A = {
|
|
10
|
+
// a: {
|
|
11
|
+
// a: 10,
|
|
12
|
+
// b: 1
|
|
13
|
+
// c: 2
|
|
14
|
+
// }
|
|
15
|
+
// b: {
|
|
16
|
+
// a: {
|
|
17
|
+
// c: 1
|
|
18
|
+
// }
|
|
19
|
+
// }
|
|
20
|
+
// }
|
|
21
|
+
// type a_path = PropertyPath<A>
|
|
22
|
+
// const ac = ["a", "c"] satisfies a_path
|
|
23
|
+
// // type a1 = (typeof ac) extends ["a", "c", ...PropertyKey[]] ? true : false
|
|
24
|
+
// type ac_type = PropertyType_<A, typeof ac>
|
|
25
|
+
export function replaceProperty(obj, property, value) {
|
|
26
|
+
if (property.length === 0)
|
|
27
|
+
return (value ?? obj);
|
|
28
|
+
if (typeof obj !== 'object')
|
|
29
|
+
throw new Error();
|
|
30
|
+
const prototype = Object.create(obj);
|
|
31
|
+
const property0 = property[0];
|
|
32
|
+
prototype[property0] = replaceProperty(obj[property0], property.slice(1), value);
|
|
33
|
+
return prototype;
|
|
34
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export interface ObservableListProtocols<T> {
|
|
2
|
+
insert(item: T, index: number): void;
|
|
3
|
+
delete(item: T, index: number): void;
|
|
4
|
+
reorder(item: T, index1: number): void;
|
|
5
|
+
}
|
|
6
|
+
export declare class ObservableList<T> extends Array<T> {
|
|
7
|
+
#private;
|
|
8
|
+
constructor(length: number);
|
|
9
|
+
constructor(...items: T[]);
|
|
10
|
+
push(...items: T[]): number;
|
|
11
|
+
pop(): T | undefined;
|
|
12
|
+
reverse(): T[];
|
|
13
|
+
sort(compareFn?: ((a: T, b: T) => number) | undefined): this;
|
|
14
|
+
shift(): T | undefined;
|
|
15
|
+
splice(start: number, deleteCount?: number, ...items: T[]): T[];
|
|
16
|
+
unshift(...items: T[]): number;
|
|
17
|
+
on<Protocol extends keyof ObservableListProtocols<T>>(protocol: Protocol, responder: ObservableListProtocols<T>[Protocol]): void;
|
|
18
|
+
off<Protocol extends keyof ObservableListProtocols<T>>(protocol: Protocol, responder: ObservableListProtocols<T>[Protocol]): void;
|
|
19
|
+
responders<Protocol extends keyof ObservableListProtocols<T>>(protocol: Protocol): {
|
|
20
|
+
insert: Set<(item: T, index: number) => void>;
|
|
21
|
+
delete: Set<(item: T, index: number) => void>;
|
|
22
|
+
reorder: Set<(item: T, index1: number) => void>;
|
|
23
|
+
}[Protocol];
|
|
24
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
export class ObservableList extends Array {
|
|
2
|
+
constructor(...itemsOrArrayLength) {
|
|
3
|
+
super(...itemsOrArrayLength);
|
|
4
|
+
}
|
|
5
|
+
push(...items) {
|
|
6
|
+
const length0 = this.length;
|
|
7
|
+
const result = super.push(...items);
|
|
8
|
+
items.forEach((item, i) => this.#emit("insert", item, length0 + i));
|
|
9
|
+
return result;
|
|
10
|
+
}
|
|
11
|
+
pop() {
|
|
12
|
+
const index = this.length - 1;
|
|
13
|
+
const result = super.pop();
|
|
14
|
+
if (index >= 0)
|
|
15
|
+
this.#emit("delete", result, index);
|
|
16
|
+
return result;
|
|
17
|
+
}
|
|
18
|
+
reverse() {
|
|
19
|
+
const final_index = this.length - 1;
|
|
20
|
+
const result = super.reverse();
|
|
21
|
+
this.forEach((item, i) => {
|
|
22
|
+
const index0 = final_index - i;
|
|
23
|
+
const index1 = i;
|
|
24
|
+
if (index0 !== index1)
|
|
25
|
+
this.#emit("reorder", item, index1);
|
|
26
|
+
});
|
|
27
|
+
return result;
|
|
28
|
+
}
|
|
29
|
+
sort(compareFn) {
|
|
30
|
+
const result = super.sort(compareFn);
|
|
31
|
+
this.forEach((item, i) => this.#emit("reorder", item, i));
|
|
32
|
+
return result;
|
|
33
|
+
}
|
|
34
|
+
shift() {
|
|
35
|
+
const length0 = this.length;
|
|
36
|
+
const result = super.shift();
|
|
37
|
+
if (length0 > 0)
|
|
38
|
+
this.#emit("delete", result, 0);
|
|
39
|
+
this.forEach((item, i) => this.#emit("reorder", item, i));
|
|
40
|
+
return result;
|
|
41
|
+
}
|
|
42
|
+
splice(start, deleteCount = 0, ...items) {
|
|
43
|
+
const deleted = super.splice(start, deleteCount, ...items);
|
|
44
|
+
deleted.forEach((item, i) => this.#emit("delete", item, start + i));
|
|
45
|
+
if ((deleteCount ?? 0) !== (items?.length ?? 0))
|
|
46
|
+
for (let i = start + (items?.length ?? 0); i < this.length; i++)
|
|
47
|
+
this.#emit("reorder", this[i], i);
|
|
48
|
+
items.forEach((item, i) => this.#emit("insert", item, start + i));
|
|
49
|
+
return deleted;
|
|
50
|
+
}
|
|
51
|
+
unshift(...items) {
|
|
52
|
+
const result = super.unshift(...items);
|
|
53
|
+
for (let i = items.length; i < this.length; i++)
|
|
54
|
+
this.#emit("reorder", this[i], i);
|
|
55
|
+
items.forEach((item, i) => this.#emit("insert", item, i));
|
|
56
|
+
return result;
|
|
57
|
+
}
|
|
58
|
+
#responders = {
|
|
59
|
+
insert: new Set(),
|
|
60
|
+
delete: new Set(),
|
|
61
|
+
reorder: new Set(),
|
|
62
|
+
};
|
|
63
|
+
#emit(protocol, ...parameters) {
|
|
64
|
+
return [...this.#responders[protocol]].map(responder => responder(...parameters));
|
|
65
|
+
}
|
|
66
|
+
on(protocol, responder) {
|
|
67
|
+
this.#responders[protocol].add(responder);
|
|
68
|
+
}
|
|
69
|
+
off(protocol, responder) {
|
|
70
|
+
this.#responders[protocol].delete(responder);
|
|
71
|
+
}
|
|
72
|
+
responders(protocol) {
|
|
73
|
+
return this.#responders[protocol];
|
|
74
|
+
}
|
|
75
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export type Deleteable<T, K extends keyof T = keyof T> = {
|
|
2
|
+
-readonly [K1 in K]?: T[K1];
|
|
3
|
+
};
|
|
4
|
+
export type PartlyDeleteable<T, K extends keyof T> = {
|
|
5
|
+
[K1 in keyof T as (K1 extends K ? never : K1)]: T[K1];
|
|
6
|
+
} & Deleteable<T, K>;
|
|
7
|
+
export type UndefinedIf<T, Condition extends boolean = boolean> = Condition extends true ? undefined : T;
|
|
8
|
+
export declare function undefinedIf<T, Condition extends boolean = boolean>(condition: Condition, item: () => T): UndefinedIf<T, Condition>;
|
|
9
|
+
export type Prefixed<Prefix extends string, T extends object> = {
|
|
10
|
+
[K in string & keyof T as `${Prefix}${K}`]: T[K];
|
|
11
|
+
};
|
|
12
|
+
export declare function prefixed<Prefix extends string, T extends object>(prefix: Prefix, o: T): Prefixed<Prefix, T>;
|
package/dist/types.js
ADDED
package/package.json
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@code-essentials/utils",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.3",
|
|
4
4
|
"description": "",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
7
7
|
"types": "./dist/index.d.ts",
|
|
8
|
+
"files": [
|
|
9
|
+
"dist/**/*"
|
|
10
|
+
],
|
|
8
11
|
"scripts": {
|
|
9
12
|
"clean": "rm -rf dist",
|
|
10
13
|
"prebuild": "npm run clean",
|
|
@@ -12,11 +15,12 @@
|
|
|
12
15
|
"build": "tsc",
|
|
13
16
|
"build:debug": "tsc -p tsconfig.debug.json",
|
|
14
17
|
"pretest": "npm run build:debug",
|
|
15
|
-
"test": "ava"
|
|
18
|
+
"test": "ava",
|
|
19
|
+
"prepublishOnly": "npm test && npm run build"
|
|
16
20
|
},
|
|
17
21
|
"devDependencies": {
|
|
18
22
|
"@ava/typescript": "^6.0.0",
|
|
19
|
-
"@code-essentials/tsconfig": "^1.0",
|
|
23
|
+
"@code-essentials/tsconfig": "^1.0.3",
|
|
20
24
|
"@types/node": "^24.5.2",
|
|
21
25
|
"ava": "^6.4.1",
|
|
22
26
|
"typescript": "^5.9.2"
|
package/.vscode/launch.json
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"configurations": [
|
|
3
|
-
{
|
|
4
|
-
"name": "test",
|
|
5
|
-
"request": "launch",
|
|
6
|
-
"runtimeArgs": [
|
|
7
|
-
"run-script",
|
|
8
|
-
"test"
|
|
9
|
-
],
|
|
10
|
-
"runtimeExecutable": "pnpm",
|
|
11
|
-
"sourceMaps": true,
|
|
12
|
-
"pauseForSourceMap": true,
|
|
13
|
-
"preLaunchTask": "npm: build:debug",
|
|
14
|
-
"internalConsoleOptions": "neverOpen",
|
|
15
|
-
"skipFiles": [
|
|
16
|
-
"<node_internals>/**"
|
|
17
|
-
],
|
|
18
|
-
"type": "node"
|
|
19
|
-
}
|
|
20
|
-
]
|
|
21
|
-
}
|
package/.vscode/tasks.json
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": "2.0.0",
|
|
3
|
-
"tasks": [
|
|
4
|
-
{
|
|
5
|
-
"label": "npm: build:debug",
|
|
6
|
-
"type": "shell",
|
|
7
|
-
"command": "npm",
|
|
8
|
-
"args": [
|
|
9
|
-
"run-script",
|
|
10
|
-
"build:debug",
|
|
11
|
-
],
|
|
12
|
-
"problemMatcher": [
|
|
13
|
-
"$tsc"
|
|
14
|
-
],
|
|
15
|
-
"presentation": {
|
|
16
|
-
"reveal": "silent",
|
|
17
|
-
"close": true,
|
|
18
|
-
},
|
|
19
|
-
"group": "build"
|
|
20
|
-
}
|
|
21
|
-
]
|
|
22
|
-
}
|
package/src/async-variable.ts
DELETED
|
@@ -1,146 +0,0 @@
|
|
|
1
|
-
interface AsyncVariableResultBase<Type> {
|
|
2
|
-
readonly type: Type
|
|
3
|
-
}
|
|
4
|
-
|
|
5
|
-
interface AsyncVariableResultResolved<T> extends AsyncVariableResultBase<"resolved"> {
|
|
6
|
-
readonly value: T
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
interface AsyncVariableResultRejected extends AsyncVariableResultBase<"rejected"> {
|
|
10
|
-
readonly error: any
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
type AsyncVariableResult<T> =
|
|
14
|
-
| AsyncVariableResultResolved<T>
|
|
15
|
-
| AsyncVariableResultRejected
|
|
16
|
-
|
|
17
|
-
export class AsyncVariable<T> implements PromiseLike<T> {
|
|
18
|
-
#result: AsyncVariableResult<T> | undefined
|
|
19
|
-
#result_res!: (res: AsyncVariableResult<T>) => void
|
|
20
|
-
#result_p!: Promise<AsyncVariableResult<T>>
|
|
21
|
-
readonly #initialization: Promise<void>
|
|
22
|
-
|
|
23
|
-
get value(): T {
|
|
24
|
-
if (!this.#result)
|
|
25
|
-
throw new Error('incomplete')
|
|
26
|
-
|
|
27
|
-
if (this.#result.type === "rejected")
|
|
28
|
-
throw this.#result.error
|
|
29
|
-
|
|
30
|
-
return this.#result.value
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
set value(value) {
|
|
34
|
-
this.set(value)
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
get error() {
|
|
38
|
-
if (!this.#result)
|
|
39
|
-
throw new Error('incomplete')
|
|
40
|
-
|
|
41
|
-
if (this.#result.type === "resolved")
|
|
42
|
-
throw new Error(this.#result.type)
|
|
43
|
-
|
|
44
|
-
return this.#result.error
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
set error(error) {
|
|
48
|
-
this.reject(error)
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
get complete() {
|
|
52
|
-
return this.#result !== undefined
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
constructor() {
|
|
56
|
-
this.#initialization = new Promise(initialized =>
|
|
57
|
-
// prevents unhandled rejection
|
|
58
|
-
this.#result_p = new Promise(res => {
|
|
59
|
-
this.#result_res = res
|
|
60
|
-
initialized()
|
|
61
|
-
})
|
|
62
|
-
)
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
async init() {
|
|
66
|
-
await this.#initialization
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
async read() {
|
|
70
|
-
await this.#initialization
|
|
71
|
-
await this.#result_p
|
|
72
|
-
return this.value
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
then<TResult1 = T, TResult2 = never>(onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | null | undefined, onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null | undefined): PromiseLike<TResult1 | TResult2> {
|
|
76
|
-
return this.read().then(onfulfilled, onrejected)
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
async #complete(throw_if_set = true) {
|
|
80
|
-
await this.#initialization
|
|
81
|
-
if (this.complete) {
|
|
82
|
-
if (throw_if_set)
|
|
83
|
-
throw new Error('already set')
|
|
84
|
-
else
|
|
85
|
-
return false
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
return true
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
async set(value: T, throw_if_set = true) {
|
|
92
|
-
if (await this.#complete(throw_if_set)) {
|
|
93
|
-
this.#result_res(this.#result = {
|
|
94
|
-
type: "resolved",
|
|
95
|
-
value
|
|
96
|
-
})
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
async reject(error: unknown, throw_if_set = true) {
|
|
101
|
-
if (await this.#complete(throw_if_set)) {
|
|
102
|
-
this.#result_res(this.#result = {
|
|
103
|
-
type: "rejected",
|
|
104
|
-
error
|
|
105
|
-
})
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
static performCallback<R = void>(fn: (cb: (err?: unknown, res?: R) => void) => void): AsyncVariable<R> {
|
|
110
|
-
const av = new AsyncVariable<R>()
|
|
111
|
-
|
|
112
|
-
fn(async (err, res) => {
|
|
113
|
-
if (err)
|
|
114
|
-
await av.error(err)
|
|
115
|
-
else
|
|
116
|
-
await av.set(<R>res)
|
|
117
|
-
})
|
|
118
|
-
|
|
119
|
-
return av
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
perform(fn: () => Promise<T>): this {
|
|
123
|
-
fn().then(value => this.set(value)).catch(error => this.reject(error))
|
|
124
|
-
|
|
125
|
-
return this
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
timeout(milliseconds: number): this {
|
|
129
|
-
AsyncVariable.wait(milliseconds).then(() => {
|
|
130
|
-
if (!this.complete)
|
|
131
|
-
this.reject("timeout")
|
|
132
|
-
})
|
|
133
|
-
|
|
134
|
-
return this
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
static perform<T>(fn: () => Promise<T>): AsyncVariable<T> {
|
|
138
|
-
return new AsyncVariable<T>().perform(fn)
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
static wait(milliseconds: number) {
|
|
142
|
-
const res = new AsyncVariable<void>()
|
|
143
|
-
setTimeout(() => res.set(), milliseconds)
|
|
144
|
-
return res
|
|
145
|
-
}
|
|
146
|
-
}
|
package/src/index.ts
DELETED
package/src/lock.spec.ts
DELETED
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import test from "ava"
|
|
2
|
-
import { Lock } from "./lock.js"
|
|
3
|
-
|
|
4
|
-
test('acquire when free', t => {
|
|
5
|
-
const lock = new Lock()
|
|
6
|
-
const context = lock.acquire()
|
|
7
|
-
context.release()
|
|
8
|
-
t.pass()
|
|
9
|
-
})
|
|
10
|
-
|
|
11
|
-
test('fail to acquire when already acquired', t => {
|
|
12
|
-
const lock = new Lock()
|
|
13
|
-
lock.acquire()
|
|
14
|
-
t.throws(() => lock.acquire(), { message: Lock.ERR_ACQUIRED })
|
|
15
|
-
})
|
|
16
|
-
|
|
17
|
-
test('disposable release', t => {
|
|
18
|
-
const lock = new Lock()
|
|
19
|
-
|
|
20
|
-
function acquire(pass: boolean) {
|
|
21
|
-
if (pass)
|
|
22
|
-
t.notThrows(() => lock.acquire())
|
|
23
|
-
else
|
|
24
|
-
t.throws(() => lock.acquire(), { message: Lock.ERR_ACQUIRED })
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function acquire_0() {
|
|
28
|
-
using _context1 = lock.acquire()
|
|
29
|
-
acquire(false)
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function acquire_1() {
|
|
33
|
-
acquire(true)
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
acquire_0()
|
|
37
|
-
acquire_1()
|
|
38
|
-
})
|
package/src/lock.ts
DELETED
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
export class Lock<Of = void> {
|
|
2
|
-
#isAcquired = false
|
|
3
|
-
|
|
4
|
-
get isAcquired() {
|
|
5
|
-
return this.#isAcquired
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
acquire(of: Of): LockContext<Of> {
|
|
9
|
-
if (this.#isAcquired)
|
|
10
|
-
throw new Error(Lock.ERR_ACQUIRED)
|
|
11
|
-
this.#isAcquired = true
|
|
12
|
-
return new LockContext(this, of, this.release.bind(this))
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
protected release(_context: LockContext<Of>) {
|
|
16
|
-
if (!this.isAcquired)
|
|
17
|
-
throw new Error()
|
|
18
|
-
|
|
19
|
-
this.#isAcquired = false
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
static readonly ERR_ACQUIRED = 'lock currently acquired'
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export class LockContext<Of> implements Disposable {
|
|
26
|
-
readonly #release: (conext: LockContext<Of>) => void
|
|
27
|
-
#isAcquired = true
|
|
28
|
-
|
|
29
|
-
constructor(
|
|
30
|
-
readonly lock: Lock<Of>,
|
|
31
|
-
readonly of: Of,
|
|
32
|
-
release: (conext: LockContext<Of>) => void
|
|
33
|
-
) {
|
|
34
|
-
this.#release = release
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
release() {
|
|
38
|
-
if (!this.#isAcquired)
|
|
39
|
-
throw new Error(LockContext.ERR_LOCK_RELEASED)
|
|
40
|
-
|
|
41
|
-
this.#isAcquired = false
|
|
42
|
-
this.#release(this)
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
[Symbol.dispose]() {
|
|
46
|
-
this.release()
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
static readonly ERR_LOCK_RELEASED = "lock context already released"
|
|
50
|
-
}
|
package/src/object.spec.ts
DELETED
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
import test from "ava"
|
|
2
|
-
import { replaceProperty } from "./object.js"
|
|
3
|
-
|
|
4
|
-
test("replace", t => {
|
|
5
|
-
interface A {
|
|
6
|
-
i: number
|
|
7
|
-
b: B
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
interface B {
|
|
11
|
-
j: number
|
|
12
|
-
c: C
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
interface C {
|
|
16
|
-
k: number
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
const a0: A = {
|
|
20
|
-
i: 0,
|
|
21
|
-
b: {
|
|
22
|
-
j: 0,
|
|
23
|
-
c: {
|
|
24
|
-
k: 0
|
|
25
|
-
},
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
t.is(a0.i, 0)
|
|
30
|
-
|
|
31
|
-
const a1 = replaceProperty(a0, ["i"], 1)
|
|
32
|
-
|
|
33
|
-
t.is(a0.i, 0)
|
|
34
|
-
t.is(a1.i, 1)
|
|
35
|
-
|
|
36
|
-
a0.b.j++
|
|
37
|
-
t.is(a0.b.j, 1)
|
|
38
|
-
t.is(a1.b.j, 1)
|
|
39
|
-
})
|
package/src/object.ts
DELETED
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
export type Values<T extends object> = T[keyof T]
|
|
2
|
-
|
|
3
|
-
export type PropertyPath<T> =
|
|
4
|
-
| []
|
|
5
|
-
| T extends object ? Values<{
|
|
6
|
-
[K in keyof T]: [K] | [K, Values<PropertyPath<T[K]>>]
|
|
7
|
-
}> : []
|
|
8
|
-
|
|
9
|
-
export type PropertyType<T, Property extends PropertyPath<T>> = PropertyType_<T, Property>
|
|
10
|
-
type PropertyType_<T, Property extends any[], Constructed extends any[] = []> =
|
|
11
|
-
(Property & Constructed) extends never ?
|
|
12
|
-
Values<{
|
|
13
|
-
[K in keyof T as Property extends [...Constructed, K, ...any[]] ? K : never]:
|
|
14
|
-
PropertyType_<T[K], Property, [...Constructed, K]>
|
|
15
|
-
}> :
|
|
16
|
-
T
|
|
17
|
-
|
|
18
|
-
// export type PropertyType<T, Property extends PropertyPath<T> & PropertyKey[]> = PropertyType_<T, Property>
|
|
19
|
-
// type PropertyType_<T, Property extends PropertyKey[], Constructed extends PropertyKey[] = []> =
|
|
20
|
-
// (Property & Constructed) extends never ?
|
|
21
|
-
// Values<{
|
|
22
|
-
// [K in keyof T as Property extends [...Constructed, K, ...PropertyKey[]] ? K : never]:
|
|
23
|
-
// PropertyType_<T[K], Property, [...Constructed, K]>
|
|
24
|
-
// }> :
|
|
25
|
-
// T
|
|
26
|
-
|
|
27
|
-
// type A = {
|
|
28
|
-
// a: {
|
|
29
|
-
// a: 10,
|
|
30
|
-
// b: 1
|
|
31
|
-
// c: 2
|
|
32
|
-
// }
|
|
33
|
-
// b: {
|
|
34
|
-
// a: {
|
|
35
|
-
// c: 1
|
|
36
|
-
// }
|
|
37
|
-
// }
|
|
38
|
-
// }
|
|
39
|
-
|
|
40
|
-
// type a_path = PropertyPath<A>
|
|
41
|
-
// const ac = ["a", "c"] satisfies a_path
|
|
42
|
-
// // type a1 = (typeof ac) extends ["a", "c", ...PropertyKey[]] ? true : false
|
|
43
|
-
// type ac_type = PropertyType_<A, typeof ac>
|
|
44
|
-
|
|
45
|
-
export function replaceProperty<T, Property extends PropertyPath<T> = PropertyPath<T>>(
|
|
46
|
-
obj: T,
|
|
47
|
-
property: Property,
|
|
48
|
-
value: any
|
|
49
|
-
): T {
|
|
50
|
-
if (property.length === 0)
|
|
51
|
-
return <T>((<any>value) ?? obj)
|
|
52
|
-
|
|
53
|
-
if (typeof obj !== 'object')
|
|
54
|
-
throw new Error()
|
|
55
|
-
|
|
56
|
-
const prototype = Object.create(obj)
|
|
57
|
-
const property0 = property[0]!
|
|
58
|
-
prototype[property0] = replaceProperty<any>((<any>obj)[property0]!, <any>property.slice(1), value)
|
|
59
|
-
return <T>prototype
|
|
60
|
-
}
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import { ObservableList, ObservableListProtocols } from "./observable-list.js"
|
|
2
|
-
import test from "ava"
|
|
3
|
-
|
|
4
|
-
test("insert", t => {
|
|
5
|
-
const list = new ObservableList<string>()
|
|
6
|
-
|
|
7
|
-
const words = ["keyboard", "mouse", "display"]
|
|
8
|
-
t.plan(words.length)
|
|
9
|
-
|
|
10
|
-
let itemInserted = ""
|
|
11
|
-
const responder: ObservableListProtocols<string>["insert"] = (item) => t.is(item, itemInserted)
|
|
12
|
-
|
|
13
|
-
list.on("insert", responder)
|
|
14
|
-
|
|
15
|
-
list.push(itemInserted = words[0]!)
|
|
16
|
-
list.push(itemInserted = words[1]!)
|
|
17
|
-
|
|
18
|
-
list.off("insert", responder)
|
|
19
|
-
|
|
20
|
-
list.push("unrelated")
|
|
21
|
-
|
|
22
|
-
list.on("insert", responder)
|
|
23
|
-
|
|
24
|
-
list.push(itemInserted = words[2]!)
|
|
25
|
-
})
|
package/src/observable-list.ts
DELETED
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
export interface ObservableListProtocols<T> {
|
|
2
|
-
insert(item: T, index: number): void
|
|
3
|
-
delete(item: T, index: number): void
|
|
4
|
-
reorder(item: T, index1: number): void
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
export class ObservableList<T> extends Array<T> {
|
|
8
|
-
constructor(length: number)
|
|
9
|
-
constructor(...items: T[])
|
|
10
|
-
constructor(...itemsOrArrayLength: any[]) {
|
|
11
|
-
super(...itemsOrArrayLength)
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
override push(...items: T[]): number {
|
|
15
|
-
const length0 = this.length
|
|
16
|
-
const result = super.push(...items)
|
|
17
|
-
|
|
18
|
-
items.forEach((item, i) => this.#emit("insert", item, length0 + i))
|
|
19
|
-
|
|
20
|
-
return result
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
override pop(): T | undefined {
|
|
24
|
-
const index = this.length - 1
|
|
25
|
-
const result = super.pop()
|
|
26
|
-
|
|
27
|
-
if (index >= 0)
|
|
28
|
-
this.#emit("delete", result!, index)
|
|
29
|
-
|
|
30
|
-
return result
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
override reverse(): T[] {
|
|
34
|
-
const final_index = this.length - 1
|
|
35
|
-
const result = super.reverse()
|
|
36
|
-
|
|
37
|
-
this.forEach((item, i) => {
|
|
38
|
-
const index0 = final_index - i
|
|
39
|
-
const index1 = i
|
|
40
|
-
|
|
41
|
-
if (index0 !== index1)
|
|
42
|
-
this.#emit("reorder", item, index1)
|
|
43
|
-
})
|
|
44
|
-
|
|
45
|
-
return result
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
override sort(compareFn?: ((a: T, b: T) => number) | undefined): this {
|
|
49
|
-
const result = super.sort(compareFn)
|
|
50
|
-
|
|
51
|
-
this.forEach((item, i) => this.#emit("reorder", item, i))
|
|
52
|
-
|
|
53
|
-
return result
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
override shift(): T | undefined {
|
|
57
|
-
const length0 = this.length
|
|
58
|
-
const result = super.shift()
|
|
59
|
-
|
|
60
|
-
if (length0 > 0)
|
|
61
|
-
this.#emit("delete", result!, 0)
|
|
62
|
-
|
|
63
|
-
this.forEach((item, i) => this.#emit("reorder", item, i))
|
|
64
|
-
|
|
65
|
-
return result
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
override splice(start: number, deleteCount = 0, ...items: T[]): T[] {
|
|
69
|
-
const deleted = super.splice(start, deleteCount, ...items)
|
|
70
|
-
|
|
71
|
-
deleted.forEach((item, i) => this.#emit("delete", item, start + i))
|
|
72
|
-
|
|
73
|
-
if ((deleteCount ?? 0) !== (items?.length ?? 0))
|
|
74
|
-
for (let i = start + (items?.length ?? 0); i < this.length; i++)
|
|
75
|
-
this.#emit("reorder", this[i]!, i)
|
|
76
|
-
|
|
77
|
-
items.forEach((item, i) => this.#emit("insert", item, start + i))
|
|
78
|
-
|
|
79
|
-
return deleted
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
override unshift(...items: T[]): number {
|
|
83
|
-
const result = super.unshift(...items)
|
|
84
|
-
|
|
85
|
-
for (let i = items.length; i < this.length; i++)
|
|
86
|
-
this.#emit("reorder", this[i]!, i)
|
|
87
|
-
|
|
88
|
-
items.forEach((item, i) => this.#emit("insert", item, i))
|
|
89
|
-
|
|
90
|
-
return result
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
#responders: { [Protocol in keyof ObservableListProtocols<T>]: Set<ObservableListProtocols<T>[Protocol]> } = {
|
|
94
|
-
insert: new Set(),
|
|
95
|
-
delete: new Set(),
|
|
96
|
-
reorder: new Set(),
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
#emit<Protocol extends keyof ObservableListProtocols<T>>(protocol: Protocol, ...parameters: Parameters<ObservableListProtocols<T>[Protocol]>): ReturnType<ObservableListProtocols<T>[Protocol]>[] {
|
|
100
|
-
return [...this.#responders[protocol]].map(responder => (<any>responder)(...parameters))
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
on<Protocol extends keyof ObservableListProtocols<T>>(protocol: Protocol, responder: ObservableListProtocols<T>[Protocol]) {
|
|
104
|
-
this.#responders[protocol].add(responder)
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
off<Protocol extends keyof ObservableListProtocols<T>>(protocol: Protocol, responder: ObservableListProtocols<T>[Protocol]) {
|
|
108
|
-
this.#responders[protocol].delete(responder)
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
responders<Protocol extends keyof ObservableListProtocols<T>>(protocol: Protocol) {
|
|
112
|
-
return this.#responders[protocol]
|
|
113
|
-
}
|
|
114
|
-
}
|
package/src/types.ts
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
export type Deleteable<T, K extends keyof T = keyof T> = {
|
|
2
|
-
-readonly [K1 in K]?: T[K1]
|
|
3
|
-
}
|
|
4
|
-
|
|
5
|
-
export type PartlyDeleteable<T, K extends keyof T> = {
|
|
6
|
-
[K1 in keyof T as (K1 extends K ? never : K1)]: T[K1]
|
|
7
|
-
} & Deleteable<T, K>
|
|
8
|
-
|
|
9
|
-
export type UndefinedIf<T, Condition extends boolean = boolean> = Condition extends true ? undefined : T
|
|
10
|
-
|
|
11
|
-
export function undefinedIf<T, Condition extends boolean = boolean>(condition: Condition, item: () => T): UndefinedIf<T, Condition> {
|
|
12
|
-
return <UndefinedIf<T, Condition>>(condition ? undefined : item())
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export type Prefixed<Prefix extends string, T extends object> = {
|
|
16
|
-
[K in string & keyof T as `${Prefix}${K}`]: T[K]
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export function prefixed<Prefix extends string, T extends object>(prefix: Prefix, o: T): Prefixed<Prefix, T> {
|
|
20
|
-
return <Prefixed<Prefix, T>>Object.fromEntries(Object.entries(o).map(([k, v]) => [`${prefix}${k}`, v] as const))
|
|
21
|
-
}
|
package/tsconfig.debug.json
DELETED