Skip to content

Commit 42abe50

Browse files
committed
fix: Prevent REST API client to be a type of function
1 parent 90f6c0e commit 42abe50

5 files changed

Lines changed: 60 additions & 33 deletions

File tree

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ jobs:
5959
cache: yarn
6060

6161
- name: Setup Mastodon
62-
run: docker compose up -d
62+
run: docker compose up -d --quiet-pull
6363

6464
- name: Install dependencies
6565
run: yarn install --frozen-lockfile

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
"test:e2e": "jest --coverage --config=jest.config.js --selectProjects e2e",
2424
"lint": "npm-run-all lint:*",
2525
"lint:eslint": "eslint --ext ts --report-unused-disable-directives --cache '{src,tests,test-utils}/**/*'",
26-
"lint:spellcheck": "cspell '{src,test,test-utils}/**/*.{ts,tsx,js,json,md}'",
26+
"lint:spellcheck": "cspell --quiet '{src,test,test-utils}/**/*.{ts,tsx,js,json,md}'",
2727
"build": "rollup -c rollup.config.js",
2828
"prepublishOnly": "yarn run build",
2929
"docs:build": "typedoc ./src/index.ts && touch ./docs/.nojekyll",

src/adapters/action/proxy.spec.ts

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ describe("RequestBuilder", () => {
3838
return {} as T;
3939
},
4040
},
41-
["root"],
41+
{ context: ["root"] },
4242
);
4343
const data = {};
4444
builder.$select("foo").bar.fetch(data);
@@ -58,7 +58,9 @@ describe("RequestBuilder", () => {
5858
return {} as T;
5959
},
6060
},
61-
["root"],
61+
{
62+
context: ["root"],
63+
},
6264
);
6365
const data = {};
6466
builder.$select("foo").bar.create(data);
@@ -78,7 +80,7 @@ describe("RequestBuilder", () => {
7880
return {} as T;
7981
},
8082
},
81-
["root"],
83+
{ context: ["root"] },
8284
);
8385
const data = {};
8486
builder.$select("AlphaBeta").gammaDelta.create(data);
@@ -87,13 +89,17 @@ describe("RequestBuilder", () => {
8789
expect(action?.data).toBe(data);
8890
});
8991

90-
it("cannot invoke without context", () => {
91-
const builder: any = createActionProxy({
92-
dispatch: Promise.resolve,
93-
});
92+
it("cannot invoke with too few context", () => {
93+
const api: any = createActionProxy(
94+
{
95+
dispatch: async <T>(_: AnyAction) => {
96+
return {} as T;
97+
},
98+
},
99+
{ context: [] },
100+
);
94101

95-
expect(() => {
96-
builder();
97-
}).toThrow();
102+
expect(() => api()).toThrow(TypeError);
103+
expect(() => api.close()).not.toThrow(TypeError);
98104
});
99105
});

src/adapters/action/proxy.ts

Lines changed: 36 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,28 @@ import {
77
} from "../../interfaces";
88
import { noop } from "../../utils/noop";
99

10+
type CreateActionProxyOptions = {
11+
readonly context?: string[];
12+
readonly applicable?: boolean;
13+
};
14+
1015
export const createActionProxy = <T>(
1116
actionDispatcher: ActionDispatcher<AnyAction>,
12-
context: string[] = [],
17+
options: CreateActionProxyOptions = {},
1318
): T => {
14-
return new Proxy(noop, {
19+
const { context = [], applicable = false } = options;
20+
21+
let target = {};
22+
const handler: ProxyHandler<typeof noop> = {
1523
get: get(actionDispatcher, context),
16-
apply: apply(actionDispatcher, context),
17-
}) as T;
24+
};
25+
26+
if (applicable) {
27+
target = noop;
28+
handler.apply = apply(actionDispatcher, context);
29+
}
30+
31+
return new Proxy(target, handler) as T;
1832
};
1933

2034
const SPECIAL_PROPERTIES = new Set([
@@ -38,37 +52,44 @@ const SPECIAL_PROPERTIES = new Set([
3852
]);
3953

4054
const get =
41-
<T>(actionDispatcher: ActionDispatcher<AnyAction>, context: string[]) =>
55+
<T>(
56+
actionDispatcher: ActionDispatcher<AnyAction>,
57+
context: readonly string[],
58+
) =>
4259
(_: unknown, property: string | symbol) => {
4360
if (typeof property === "string" && SPECIAL_PROPERTIES.has(property)) {
4461
return;
4562
}
4663
if (typeof property === "symbol") {
4764
return;
4865
}
49-
if (property === "$select") {
50-
return createActionProxy<T>(actionDispatcher, [...context, property]);
66+
if (property.startsWith("$")) {
67+
return createActionProxy<T>(actionDispatcher, {
68+
context: [...context, property],
69+
applicable: true,
70+
});
5171
}
52-
return createActionProxy<T>(actionDispatcher, [
53-
...context,
54-
snakeCase(property),
55-
]);
72+
return createActionProxy<T>(actionDispatcher, {
73+
context: [...context, snakeCase(property)],
74+
applicable: true,
75+
});
5676
};
5777

5878
const apply =
5979
<T>(actionDispatcher: ActionDispatcher<AnyAction>, context: string[]) =>
6080
(_1: unknown, _2: unknown, args: unknown[]): unknown => {
6181
const action = context.pop();
6282

83+
/* istanbul ignore next */
6384
if (action == undefined) {
6485
throw new Error("No action specified");
6586
}
6687

6788
if (action === "$select") {
68-
return createActionProxy<T>(actionDispatcher, [
69-
...context,
70-
...(args as string[]),
71-
]);
89+
return createActionProxy<T>(actionDispatcher, {
90+
context: [...context, ...(args as string[])],
91+
applicable: true,
92+
});
7293
}
7394

7495
const path = "/" + context.join("/");

src/adapters/clients.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,9 @@ export const createRestAPIClient = (
3030
const http = new HttpNativeImpl(serializer, config, logger);
3131
const hook = new HttpActionDispatcherHookMastodon(http);
3232
const actionDispatcher = new HttpActionDispatcher(http, hook);
33-
const actionProxy = createActionProxy(actionDispatcher, [
34-
"api",
35-
]) as mastodon.rest.Client;
33+
const actionProxy = createActionProxy(actionDispatcher, {
34+
context: ["api"],
35+
}) as mastodon.rest.Client;
3636
return actionProxy;
3737
};
3838

@@ -45,9 +45,9 @@ export const createOAuthAPIClient = (
4545
const http = new HttpNativeImpl(serializer, config, logger);
4646
const hook = new HttpActionDispatcherHookMastodon(http);
4747
const actionDispatcher = new HttpActionDispatcher(http, hook);
48-
const actionProxy = createActionProxy(actionDispatcher, [
49-
"oauth",
50-
]) as mastodon.oauth.Client;
48+
const actionProxy = createActionProxy(actionDispatcher, {
49+
context: ["oauth"],
50+
}) as mastodon.oauth.Client;
5151
return actionProxy;
5252
};
5353

0 commit comments

Comments
 (0)