@pippenly/ts-utils 1.0.4 → 1.0.5
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 +70 -0
- package/package.json +1 -1
- package/src/result/sync/index.ts +5 -0
- package/src/scope/scope.ts +63 -54
package/README.md
CHANGED
|
@@ -40,6 +40,8 @@ For two way binding, `model(sourceResin, element, options?)` exists, where eleme
|
|
|
40
40
|
throttle / debounce options and custom get/set if needed.
|
|
41
41
|
|
|
42
42
|
```ts
|
|
43
|
+
import { resin, bind, model, watchEffect } from "@pippenly/ts-utils/dom";
|
|
44
|
+
|
|
43
45
|
const name = resin("");
|
|
44
46
|
const m = model(name, document.querySelector<HTMLInputElement>("#name")!, {
|
|
45
47
|
debounce: 300
|
|
@@ -59,6 +61,74 @@ const ageModel = model(age, document.querySelector<HTMLInputElement>("#age")!, {
|
|
|
59
61
|
});
|
|
60
62
|
```
|
|
61
63
|
|
|
64
|
+
# Scope
|
|
65
|
+
Creating a scope with a dependency injection system
|
|
66
|
+
```ts
|
|
67
|
+
import { asyncScope, Err, Ok, token, type AsyncRunnable, type Result } from "@pippenly/ts-utils";
|
|
68
|
+
|
|
69
|
+
interface Logger {
|
|
70
|
+
info: (message: string) => void;
|
|
71
|
+
warn: (message: string) => void;
|
|
72
|
+
error: (message: string) => void;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
interface HttpClient {
|
|
76
|
+
get: (url: string) => Promise<any>;
|
|
77
|
+
post: (url: string, data: any) => Promise<any>;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
type HttpRunnable = AsyncRunnable<typeof httpScope, Result<any, string>>;
|
|
81
|
+
|
|
82
|
+
const httpScope = asyncScope(Logger, HttpClient)
|
|
83
|
+
.provide(Logger, new ConsoleLogger())
|
|
84
|
+
.provide(HttpClient, new BrowserHttpClient());
|
|
85
|
+
|
|
86
|
+
const sendMessage: HttpRunnable = async (get): AsyncResult<any, string> => {
|
|
87
|
+
const logger = get(Logger);
|
|
88
|
+
const httpClient = get(HttpClient);
|
|
89
|
+
logger.info("Sending message...");
|
|
90
|
+
try {
|
|
91
|
+
const response = await httpClient.post("/api/messages", { text: "Hello, World!" });
|
|
92
|
+
logger.info("Message sent successfully: " + JSON.stringify(response));
|
|
93
|
+
return Ok(response);
|
|
94
|
+
} catch (error) {
|
|
95
|
+
logger.error("Failed to send message: " + error);
|
|
96
|
+
return Err("Failed to send message");
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const sendHealth: HttpRunnable = async (get): AsyncResult<any, string> => {
|
|
101
|
+
const logger = get(Logger);
|
|
102
|
+
const httpClient = get(HttpClient);
|
|
103
|
+
logger.info("Checking server health...");
|
|
104
|
+
try {
|
|
105
|
+
const response = await httpClient.get("/api/health");
|
|
106
|
+
logger.info("Server health: " + JSON.stringify(response));
|
|
107
|
+
return Ok(response);
|
|
108
|
+
}
|
|
109
|
+
catch (error) {
|
|
110
|
+
logger.error("Failed to check server health: " + error);
|
|
111
|
+
return Err("Failed to check server health");
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
async function run() {
|
|
116
|
+
const sendMessageResult = await httpScope.run(sendMessage);
|
|
117
|
+
if (sendMessageResult.ok) {
|
|
118
|
+
console.log("Message response:", sendMessageResult.value);
|
|
119
|
+
} else {
|
|
120
|
+
console.error("Error sending message:", sendMessageResult.error);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const healthResult = await httpScope.run(sendHealth);
|
|
124
|
+
if (healthResult.ok) {
|
|
125
|
+
console.log("Health response:", healthResult.value);
|
|
126
|
+
} else {
|
|
127
|
+
console.error("Error checking health:", healthResult.error);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
```
|
|
62
132
|
|
|
63
133
|
## Utilities
|
|
64
134
|
|
package/package.json
CHANGED
package/src/result/sync/index.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { AsyncResult } from "../async/index.js";
|
|
2
|
+
|
|
1
3
|
export type Ok<T> = {
|
|
2
4
|
ok: true;
|
|
3
5
|
value: T;
|
|
@@ -19,6 +21,9 @@ export function Err<E>(error: E, code?: number): Err<E> {
|
|
|
19
21
|
return { ok: false, error, code };
|
|
20
22
|
}
|
|
21
23
|
|
|
24
|
+
export type InferOk<R> = R extends Result<infer T, any> | AsyncResult<infer T, any> ? T : never;
|
|
25
|
+
export type InferErr<R> = R extends Result<any, infer E> | AsyncResult<any, infer E> ? E : never;
|
|
26
|
+
|
|
22
27
|
export function tryCatch<T, E>(fn: () => T, onErr: (e: unknown) => E): Result<T, E> {
|
|
23
28
|
try {
|
|
24
29
|
return Ok(fn());
|
package/src/scope/scope.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type Result, Ok, Err } from "@/result/index.js";
|
|
1
|
+
import { type Result, Ok, Err, type InferOk, type InferErr, type AsyncResult } from "@/result/index.js";
|
|
2
2
|
|
|
3
3
|
type Brand<K> = {
|
|
4
4
|
__brand: K;
|
|
@@ -8,59 +8,66 @@ type SimpleError = {
|
|
|
8
8
|
message: string;
|
|
9
9
|
};
|
|
10
10
|
|
|
11
|
-
type Ctor<T> = new (...args: any[]) => T;
|
|
12
|
-
|
|
13
11
|
export type InjectorError = SimpleError & Brand<"InjectorError">;
|
|
14
12
|
|
|
15
|
-
export type
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
13
|
+
export type Token<T> = {
|
|
14
|
+
readonly id: symbol;
|
|
15
|
+
readonly name: string;
|
|
16
|
+
readonly _type: T;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export function token<T>(name: string): Token<T> {
|
|
20
|
+
return { id: Symbol(name), name } as Token<T>;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
type TokenType<T> = T extends Token<infer U> ? U : never;
|
|
24
|
+
|
|
25
|
+
export type Scope<TDeps extends readonly Token<any>[]> = {
|
|
26
|
+
provide: <C extends TDeps[number]>(token: C, instance: TokenType<C>) => Scope<TDeps>;
|
|
27
|
+
run: <R>(fn: (get: <C extends TDeps[number]>(token: C) => TokenType<C>) => R) => R;
|
|
19
28
|
[Symbol.dispose]: () => void;
|
|
20
29
|
};
|
|
21
30
|
|
|
22
|
-
export type AsyncScope<TDeps extends readonly
|
|
23
|
-
provide: <C extends TDeps[number]>(
|
|
24
|
-
run: <R>(fn: (get: <C extends TDeps[number]>(
|
|
25
|
-
finalize: <R>(fn: (get: <C extends TDeps[number]>(ctor: C) => Result<InstanceType<C>, InjectorError>) => Promise<R>) => Promise<R>;
|
|
31
|
+
export type AsyncScope<TDeps extends readonly Token<any>[]> = {
|
|
32
|
+
provide: <C extends TDeps[number]>(token: C, instance: TokenType<C>) => AsyncScope<TDeps>;
|
|
33
|
+
run: <R>(fn: (get: <C extends TDeps[number]>(token: C) => TokenType<C>) => Promise<R>) => Promise<R>;
|
|
26
34
|
[Symbol.asyncDispose]: () => Promise<void>;
|
|
27
35
|
};
|
|
28
36
|
|
|
29
|
-
export function scope<TDeps extends ReadonlyArray<
|
|
37
|
+
export function scope<TDeps extends ReadonlyArray<Token<any>>>(...deps: TDeps): Scope<TDeps> {
|
|
30
38
|
return createScope(deps, new Map());
|
|
31
39
|
}
|
|
32
40
|
|
|
33
|
-
export function asyncScope<TDeps extends ReadonlyArray<
|
|
41
|
+
export function asyncScope<TDeps extends ReadonlyArray<Token<any>>>(...deps: TDeps): AsyncScope<TDeps> {
|
|
34
42
|
return createAsyncScope(deps, new Map());
|
|
35
43
|
}
|
|
36
44
|
|
|
37
|
-
function createScope<TDeps extends ReadonlyArray<
|
|
45
|
+
function createScope<TDeps extends ReadonlyArray<Token<any>>>(
|
|
38
46
|
deps: TDeps,
|
|
39
|
-
instances: Map<
|
|
47
|
+
instances: Map<symbol, any>,
|
|
40
48
|
): Scope<TDeps> {
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
|
|
49
|
+
const ids = new Set(deps.map(d => d.id));
|
|
50
|
+
|
|
51
|
+
const find = <C extends TDeps[number]>(t: C): TokenType<C> => {
|
|
52
|
+
if (!ids.has(t.id)) {
|
|
53
|
+
throw new Error(`Dependency "${t.name}" is not registered in this scope.`);
|
|
44
54
|
}
|
|
45
|
-
if (!instances.has(
|
|
46
|
-
|
|
55
|
+
if (!instances.has(t.id)) {
|
|
56
|
+
throw new Error(`Dependency "${t.name}" has not been provided.`);
|
|
47
57
|
}
|
|
48
|
-
return
|
|
58
|
+
return instances.get(t.id)!;
|
|
49
59
|
};
|
|
50
60
|
|
|
51
61
|
return {
|
|
52
|
-
provide<C extends TDeps[number]>(
|
|
53
|
-
if (!
|
|
54
|
-
|
|
62
|
+
provide<C extends TDeps[number]>(t: C, instance: TokenType<C>): Scope<TDeps> {
|
|
63
|
+
if (!ids.has(t.id)) {
|
|
64
|
+
throw new Error(`Cannot provide "${t.name}": not registered in this scope.`);
|
|
55
65
|
}
|
|
56
66
|
const next = new Map(instances);
|
|
57
|
-
next.set(
|
|
58
|
-
return
|
|
59
|
-
},
|
|
60
|
-
run<R>(fn: (get: <C extends TDeps[number]>(ctor: C) => Result<InstanceType<C>, InjectorError>) => R): R {
|
|
61
|
-
return fn(find);
|
|
67
|
+
next.set(t.id, instance);
|
|
68
|
+
return createScope(deps, next);
|
|
62
69
|
},
|
|
63
|
-
|
|
70
|
+
run<R>(fn: (get: <C extends TDeps[number]>(t: C) => TokenType<C>) => R): R {
|
|
64
71
|
return fn(find);
|
|
65
72
|
},
|
|
66
73
|
[Symbol.dispose]() {
|
|
@@ -69,34 +76,33 @@ function createScope<TDeps extends ReadonlyArray<Ctor<any>>>(
|
|
|
69
76
|
};
|
|
70
77
|
}
|
|
71
78
|
|
|
72
|
-
function createAsyncScope<TDeps extends ReadonlyArray<
|
|
79
|
+
function createAsyncScope<TDeps extends ReadonlyArray<Token<any>>>(
|
|
73
80
|
deps: TDeps,
|
|
74
|
-
instances: Map<
|
|
81
|
+
instances: Map<symbol, any>,
|
|
75
82
|
): AsyncScope<TDeps> {
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
|
|
83
|
+
const ids = new Set(deps.map(d => d.id));
|
|
84
|
+
|
|
85
|
+
const find = <C extends TDeps[number]>(t: C): TokenType<C> => {
|
|
86
|
+
if (!ids.has(t.id)) {
|
|
87
|
+
throw new Error(`Dependency "${t.name}" is not registered in this scope.`);
|
|
79
88
|
}
|
|
80
|
-
if (!instances.has(
|
|
81
|
-
|
|
89
|
+
if (!instances.has(t.id)) {
|
|
90
|
+
throw new Error(`Dependency "${t.name}" has not been provided.`);
|
|
82
91
|
}
|
|
83
|
-
return
|
|
92
|
+
return instances.get(t.id)!;
|
|
84
93
|
};
|
|
85
94
|
|
|
86
95
|
return {
|
|
87
|
-
provide<C extends TDeps[number]>(
|
|
88
|
-
if (!
|
|
89
|
-
|
|
96
|
+
provide<C extends TDeps[number]>(t: C, instance: TokenType<C>): AsyncScope<TDeps> {
|
|
97
|
+
if (!ids.has(t.id)) {
|
|
98
|
+
throw new Error(`Cannot provide "${t.name}": not registered in this scope.`);
|
|
90
99
|
}
|
|
91
100
|
const next = new Map(instances);
|
|
92
|
-
next.set(
|
|
93
|
-
return
|
|
101
|
+
next.set(t.id, instance);
|
|
102
|
+
return createAsyncScope(deps, next);
|
|
94
103
|
},
|
|
95
|
-
async run<R>(fn: (get: <C extends TDeps[number]>(
|
|
96
|
-
return fn(find);
|
|
97
|
-
},
|
|
98
|
-
async finalize<R>(fn: (get: <C extends TDeps[number]>(ctor: C) => Result<InstanceType<C>, InjectorError>) => Promise<R>): Promise<R> {
|
|
99
|
-
return fn(find);
|
|
104
|
+
async run<R>(fn: (get: <C extends TDeps[number]>(t: C) => TokenType<C>) => Promise<R>): Promise<R> {
|
|
105
|
+
return await fn(find);
|
|
100
106
|
},
|
|
101
107
|
async [Symbol.asyncDispose]() {
|
|
102
108
|
instances.clear();
|
|
@@ -104,9 +110,12 @@ function createAsyncScope<TDeps extends ReadonlyArray<Ctor<any>>>(
|
|
|
104
110
|
};
|
|
105
111
|
}
|
|
106
112
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
+
type InferDeps<T> = T extends Scope<infer D> | AsyncScope<infer D> ? D : never;
|
|
114
|
+
|
|
115
|
+
export type Runnable<TScope extends Scope<any>, TResult> = (
|
|
116
|
+
get: <C extends InferDeps<TScope>[number]>(token: C) => TokenType<C>
|
|
117
|
+
) => TResult;
|
|
118
|
+
|
|
119
|
+
export type AsyncRunnable<TScope extends AsyncScope<any>, TResult> = (
|
|
120
|
+
get: <C extends InferDeps<TScope>[number]>(token: C) => TokenType<C>
|
|
121
|
+
) => Promise<TResult>;
|