Skip to content

Commit d343d06

Browse files
committed
wip
1 parent c776061 commit d343d06

File tree

11 files changed

+153
-100
lines changed

11 files changed

+153
-100
lines changed

src/adapters/action/dispatcher-http-hook-mastodon.spec.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,10 @@ describe("DispatcherHttp", () => {
2525
.mockRejectedValueOnce(
2626
new MastoHttpError({ statusCode: 404, message: "Not Found" }),
2727
)
28-
.mockResolvedValueOnce({ id: "1", url: "https://example.com" });
28+
.mockResolvedValueOnce({
29+
headers: new Headers(),
30+
data: { id: "1", url: "https://example.com" },
31+
});
2932

3033
const media = await dispatcher.dispatch({
3134
type: "create",

src/adapters/action/dispatcher-http-hook-mastodon.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ async function waitForMediaAttachment(
6666
try {
6767
await sleep(1000);
6868

69-
const processing = await http.get<mastodon.v1.MediaAttachment>(
69+
const { data: processing } = await http.get<mastodon.v1.MediaAttachment>(
7070
`/api/v1/media/${id}`,
7171
);
7272

src/adapters/action/dispatcher-http.ts

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,24 +3,41 @@ import {
33
type ActionDispatcher,
44
type ActionDispatcherHook,
55
type Http,
6+
type HttpResponse,
67
} from "../../interfaces/index.js";
8+
import { type Paginator } from "../../mastodon/paginator.js";
79
import { PaginatorHttp } from "./paginator-http.js";
810

9-
export type HttpActionType = "fetch" | "create" | "update" | "remove" | "list";
11+
export type HttpActionMap = {
12+
fetch: Promise<HttpResponse<unknown>>;
13+
create: Promise<HttpResponse<unknown>>;
14+
update: Promise<HttpResponse<unknown>>;
15+
remove: Promise<HttpResponse<unknown>>;
16+
list: Paginator<unknown>;
17+
};
18+
19+
export type HttpActionType = keyof HttpActionMap;
1020
export type HttpAction = Action<HttpActionType>;
1121

12-
export class HttpActionDispatcher implements ActionDispatcher<HttpAction> {
22+
export class HttpActionDispatcher implements ActionDispatcher<HttpActionMap> {
1323
constructor(
1424
private readonly http: Http,
1525
private readonly hook: ActionDispatcherHook<HttpAction>,
1626
) {}
1727

18-
dispatch<T>(action: HttpAction): T | Promise<T> {
28+
dispatch(
29+
action: Action<"fetch" | "create" | "update" | "remove">,
30+
): Promise<HttpResponse<unknown>>;
31+
dispatch(action: Action<"list">): Paginator<unknown>;
32+
dispatch(action: Action<HttpActionType>): unknown {
1933
if (this.hook) {
2034
action = this.hook.beforeDispatch(action);
2135
}
2236

23-
let result = this.hook.dispatch(action) as T | Promise<T> | false;
37+
let result = this.hook.dispatch(action) as
38+
| Promise<HttpResponse<unknown>>
39+
| Paginator<unknown>
40+
| false;
2441
if (result !== false) {
2542
return result;
2643
}
@@ -43,16 +60,16 @@ export class HttpActionDispatcher implements ActionDispatcher<HttpAction> {
4360
break;
4461
}
4562
case "list": {
46-
result = new PaginatorHttp(this.http, action.path, action.data) as T;
63+
result = new PaginatorHttp(this.http, action.path, action.data);
4764
break;
4865
}
4966
}
5067

5168
/* eslint-disable unicorn/prefer-ternary, prettier/prettier */
5269
if (result instanceof Promise) {
53-
return result.then((result) => this.hook?.afterDispatch(action, result)) as Promise<T>;
70+
return result.then((result) => this.hook?.afterDispatch(action, result)) as Promise<HttpResponse<unknown>>;
5471
} else {
55-
return this.hook.afterDispatch(action, result) as T;
72+
return this.hook.afterDispatch(action, result) as Paginator<unknown>;
5673
}
5774
/* eslint-enable unicorn/prefer-ternary, prettier/prettier */
5875
}

src/adapters/action/dispatcher-ws.ts

Lines changed: 27 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,17 @@ import {
99
import { MastoUnexpectedError } from "../errors/index.js";
1010
import { WebSocketSubscription } from "../ws/index.js";
1111

12-
type WebSocketActionType = "close" | "prepare" | "subscribe";
12+
type WebSocketActionMap = {
13+
close: undefined;
14+
prepare: Promise<WebSocket>;
15+
subscribe: WebSocketSubscription;
16+
};
17+
18+
type WebSocketActionType = keyof WebSocketActionMap;
1319
type WebSocketAction = Action<WebSocketActionType>;
1420

1521
export class WebSocketActionDispatcher
16-
implements ActionDispatcher<WebSocketAction>
22+
implements ActionDispatcher<WebSocketActionMap>
1723
{
1824
constructor(
1925
private readonly connector: WebSocketConnector,
@@ -22,31 +28,34 @@ export class WebSocketActionDispatcher
2228
private readonly logger?: Logger,
2329
) {}
2430

25-
dispatch<T>(action: WebSocketAction): T {
31+
dispatch(action: Action<"close">): void;
32+
dispatch(action: Action<"prepare">): Promise<WebSocket>;
33+
dispatch(action: Action<"subscribe">): WebSocketSubscription;
34+
dispatch(action: WebSocketAction): unknown {
2635
if (action.type === "close") {
2736
this.connector.kill();
28-
return {} as T;
37+
return;
2938
}
3039

3140
if (action.type === "prepare") {
32-
return this.connector.acquire() as T;
41+
return this.connector.acquire();
3342
}
3443

35-
if (action.type !== "subscribe") {
36-
throw new MastoUnexpectedError(`Unknown action type ${action.type}`);
44+
if (action.type === "subscribe") {
45+
const data = action.data ?? {};
46+
const stream = action.path.replace(/^\//, "").replaceAll("/", ":");
47+
48+
return new WebSocketSubscription(
49+
this.connector,
50+
this.counter,
51+
this.serializer,
52+
stream,
53+
this.logger,
54+
{ ...data },
55+
);
3756
}
3857

39-
const data = action.data ?? {};
40-
const stream = action.path.replace(/^\//, "").replaceAll("/", ":");
41-
42-
return new WebSocketSubscription(
43-
this.connector,
44-
this.counter,
45-
this.serializer,
46-
stream,
47-
this.logger,
48-
{ ...data },
49-
) as T;
58+
throw new MastoUnexpectedError(`Unknown action type ${action.type}`);
5059
}
5160

5261
[Symbol.dispose](): void {

src/adapters/action/proxy.ts

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@ import { snakeCase } from "change-case";
22

33
import {
44
type ActionDispatcher,
5-
type AnyAction,
65
type HttpMetaParams,
76
} from "../../interfaces/index.js";
7+
import { isRecord } from "../../utils/is-record.js";
88
import { noop } from "../../utils/noop.js";
99

1010
type CreateActionProxyOptions = {
@@ -13,7 +13,7 @@ type CreateActionProxyOptions = {
1313
};
1414

1515
export const createActionProxy = <T>(
16-
actionDispatcher: ActionDispatcher<AnyAction>,
16+
actionDispatcher: ActionDispatcher,
1717
options: CreateActionProxyOptions = {},
1818
): T => {
1919
const { context = [], applicable = false } = options;
@@ -52,10 +52,7 @@ const SPECIAL_PROPERTIES = new Set([
5252
]);
5353

5454
const get =
55-
<T>(
56-
actionDispatcher: ActionDispatcher<AnyAction>,
57-
context: readonly string[],
58-
) =>
55+
<T>(actionDispatcher: ActionDispatcher, context: readonly string[]) =>
5956
(_: unknown, property: string | symbol) => {
6057
if (typeof property === "string" && SPECIAL_PROPERTIES.has(property)) {
6158
return;
@@ -79,7 +76,7 @@ const get =
7976
};
8077

8178
const apply =
82-
<T>(actionDispatcher: ActionDispatcher<AnyAction>, context: string[]) =>
79+
<T>(actionDispatcher: ActionDispatcher, context: string[]) =>
8380
(_1: unknown, _2: unknown, args: unknown[]): unknown => {
8481
const action = context.pop();
8582

@@ -98,10 +95,16 @@ const apply =
9895
const path = "/" + context.join("/");
9996
const [data, meta] = args;
10097

101-
return actionDispatcher.dispatch<T>({
98+
const result = actionDispatcher.dispatch({
10299
type: action,
103100
path,
104101
data,
105102
meta: meta as HttpMetaParams,
106103
});
104+
105+
if (action === "$raw") {
106+
return result;
107+
} else {
108+
return isRecord(result) && "data" in result ? result.data : result;
109+
}
107110
};

src/adapters/http/base-http.ts

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,74 +3,74 @@ import {
33
type Http,
44
type HttpMetaParams,
55
type HttpRequestParams,
6-
type HttpRequestResult,
6+
type HttpResponse,
77
} from "../../interfaces/index.js";
88

99
export abstract class BaseHttp implements Http {
10-
abstract request(params: HttpRequestParams): Promise<HttpRequestResult>;
10+
abstract request<T>(params: HttpRequestParams): Promise<HttpResponse<T>>;
1111

1212
get<T>(
1313
path: string,
1414
data?: unknown,
1515
meta: HttpMetaParams<Encoding> = {},
16-
): Promise<T> {
16+
): Promise<HttpResponse<T>> {
1717
return this.request({
1818
method: "GET",
1919
path,
2020
search: data as Record<string, unknown>,
2121
...meta,
22-
}).then((response) => response.data as T);
22+
});
2323
}
2424

2525
post<T>(
2626
path: string,
2727
data?: unknown,
2828
meta: HttpMetaParams<Encoding> = {},
29-
): Promise<T> {
29+
): Promise<HttpResponse<T>> {
3030
return this.request({
3131
method: "POST",
3232
path,
3333
body: data as Record<string, unknown>,
3434
...meta,
35-
}).then((response) => response.data as T);
35+
});
3636
}
3737

3838
delete<T>(
3939
path: string,
4040
data?: unknown,
4141
meta: HttpMetaParams<Encoding> = {},
42-
): Promise<T> {
42+
): Promise<HttpResponse<T>> {
4343
return this.request({
4444
method: "DELETE",
4545
path,
4646
body: data as Record<string, unknown>,
4747
...meta,
48-
}).then((response) => response.data as T);
48+
});
4949
}
5050

5151
put<T>(
5252
path: string,
5353
data?: unknown,
5454
meta: HttpMetaParams<Encoding> = {},
55-
): Promise<T> {
55+
): Promise<HttpResponse<T>> {
5656
return this.request({
5757
method: "PUT",
5858
path,
5959
body: data as Record<string, unknown>,
6060
...meta,
61-
}).then((response) => response.data as T);
61+
});
6262
}
6363

6464
patch<T>(
6565
path: string,
6666
data?: unknown,
6767
meta: HttpMetaParams<Encoding> = {},
68-
): Promise<T> {
68+
): Promise<HttpResponse<T>> {
6969
return this.request({
7070
method: "PATCH",
7171
path,
7272
body: data as Record<string, unknown>,
7373
...meta,
74-
}).then((response) => response.data as T);
74+
});
7575
}
7676
}

src/adapters/http/http-native-impl.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import {
22
type Http,
33
type HttpConfig,
44
type HttpRequestParams,
5-
type HttpRequestResult,
5+
type HttpResponse,
66
type Logger,
77
type Serializer,
88
} from "../../interfaces/index.js";
@@ -24,7 +24,7 @@ export class HttpNativeImpl extends BaseHttp implements Http {
2424
super();
2525
}
2626

27-
async request(params: HttpRequestParams): Promise<HttpRequestResult> {
27+
async request<T>(params: HttpRequestParams): Promise<HttpResponse<T>> {
2828
const request = this.createRequest(params);
2929

3030
try {
@@ -52,7 +52,7 @@ export class HttpNativeImpl extends BaseHttp implements Http {
5252

5353
return {
5454
headers: response.headers,
55-
data,
55+
data: data as T,
5656
};
5757
} catch (error) {
5858
this.logger?.log("debug", `HTTP failed`, error);

src/interfaces/action.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { type HttpMetaParams } from "./http.js";
22
import { type Encoding } from "./serializer.js";
33

4-
export interface Action<T extends string> {
4+
export interface Action<T> {
55
readonly type: T;
66
readonly path: string;
77
readonly data: unknown;
@@ -10,8 +10,10 @@ export interface Action<T extends string> {
1010

1111
export type AnyAction = Action<string>;
1212

13-
export interface ActionDispatcher<T extends AnyAction> {
14-
dispatch<U>(action: T): U | Promise<U>;
13+
export type ActionMap = { [key in string]: unknown };
14+
15+
export interface ActionDispatcher<U extends ActionMap = ActionMap> {
16+
dispatch<T extends keyof U>(action: Action<T>): U[T];
1517
[Symbol.dispose]?(): void;
1618
}
1719

src/interfaces/http.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,19 +12,19 @@ export type HttpRequestParams = HttpMetaParams<Encoding> & {
1212
readonly body?: Record<string, unknown>;
1313
};
1414

15-
export interface HttpRequestResult {
15+
export interface HttpResponse<T = unknown> {
1616
headers: Headers;
17-
data: unknown;
17+
data: T;
1818
}
1919

2020
export type HttpMethod = <T>(
2121
path: string,
2222
data?: unknown,
2323
meta?: HttpMetaParams<Encoding>,
24-
) => Promise<T>;
24+
) => Promise<HttpResponse<T>>;
2525

2626
export interface Http {
27-
readonly request: (params: HttpRequestParams) => Promise<HttpRequestResult>;
27+
readonly request: <T>(params: HttpRequestParams) => Promise<HttpResponse<T>>;
2828
readonly get: HttpMethod;
2929
readonly post: HttpMethod;
3030
readonly patch: HttpMethod;

src/mastodon/endpoint.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
type OptionalRecord = {
2+
[key in string]?: unknown;
3+
};
4+
5+
type RawResponse = {
6+
data: Response;
7+
headers: Headers;
8+
};
9+
10+
export type Endpoint<Params, Meta, Response> = Params extends
11+
| OptionalRecord
12+
| undefined
13+
? {
14+
(params?: Params, meta?: Meta): Promise<Response>;
15+
/** @experimental */
16+
$raw(params?: Params, meta?: Meta): Promise<RawResponse>;
17+
}
18+
: {
19+
(params: Params, meta?: Meta): Promise<Response>;
20+
/** @experimental */
21+
$raw(params: Params, meta?: Meta): Promise<RawResponse>;
22+
};

0 commit comments

Comments
 (0)