Skip to content

Commit

Permalink
Merge pull request #975 from neet/ts-5.2.0
Browse files Browse the repository at this point in the history
Add experimental support for explicit resource management
  • Loading branch information
neet authored Oct 28, 2023
2 parents 5499ca0 + f22af2a commit 6ad8afa
Show file tree
Hide file tree
Showing 58 changed files with 4,583 additions and 4,474 deletions.
10 changes: 5 additions & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
name: 'CI'
name: "CI"

on:
push:
branches:
- main
pull_request:
branches:
- '*'
- "*"
workflow_call:
workflow_dispatch:

Expand All @@ -23,7 +23,7 @@ jobs:
- name: Use Node.js
uses: actions/setup-node@v3
with:
node-version: 18
node-version: "20.6.1"
cache: yarn

- name: Install dependencies
Expand All @@ -42,7 +42,7 @@ jobs:
with:
flags: unit
token: ${{ secrets.CODECOV_TOKEN }}

test-e2e:
name: E2E Test
runs-on: ubuntu-latest
Expand Down Expand Up @@ -79,7 +79,7 @@ jobs:
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: 18
node-version: "20.6.1"
cache: yarn

- name: Setup Mastodon
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/publish-docs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ jobs:
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
node-version: "20.6.1"
cache: yarn

- name: Setup Pages
Expand All @@ -34,7 +34,7 @@ jobs:
- name: Upload artifact
uses: actions/upload-pages-artifact@v1
with:
path: './docs'
path: "./docs"

deploy:
runs-on: ubuntu-latest
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ name: Publish
on:
workflow_dispatch:
schedule:
- cron: '0 0 * * *'
- cron: "0 0 * * *"

jobs:
test:
Expand All @@ -26,7 +26,7 @@ jobs:
- name: Use Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
node-version: "20.6.1"
cache: yarn

- run: yarn install --frozen-lockfiles
Expand Down
4 changes: 2 additions & 2 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ export default {
"ts-jest",
{
tsconfig: {
target: "esnext",
module: "esnext",
target: "ES2022",
module: "ES2022",
},
},
],
Expand Down
53 changes: 27 additions & 26 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,46 +31,47 @@
},
"dependencies": {
"change-case": "^4.1.2",
"events-to-async": "^2.0.0",
"events-to-async": "^2.0.1",
"isomorphic-ws": "^5.0.0",
"ts-custom-error": "^3.3.1",
"ws": "^8.13.0"
},
"devDependencies": {
"@rollup/plugin-commonjs": "^25.0.3",
"@rollup/plugin-json": "^6.0.0",
"@rollup/plugin-commonjs": "^25.0.7",
"@rollup/plugin-json": "^6.0.1",
"@rollup/plugin-typescript": "^11.1.5",
"@semantic-release/changelog": "^6.0.3",
"@semantic-release/git": "^10.0.1",
"@types/jest": "^29.5.2",
"@types/node": "^20.3.3",
"@types/proper-lockfile": "^4.1.2",
"@types/ws": "^8.5.5",
"@typescript-eslint/eslint-plugin": "^5.62.0",
"@typescript-eslint/parser": "^5.62.0",
"conventional-changelog-conventionalcommits": "^6.1.0",
"cspell": "^6.31.1",
"eslint": "^8.44.0",
"eslint-config-prettier": "^8.8.0",
"eslint-plugin-import": "^2.27.5",
"eslint-plugin-prettier": "^4.0.0",
"@types/jest": "^29.5.6",
"@types/node": "^20.8.9",
"@types/proper-lockfile": "^4.1.3",
"@types/ws": "^8.5.8",
"@typescript-eslint/eslint-plugin": "^6.9.0",
"@typescript-eslint/parser": "^6.9.0",
"conventional-changelog-conventionalcommits": "^7.0.1",
"cspell": "^7.3.8",
"eslint": "^8.52.0",
"eslint-config-prettier": "^9.0.0",
"eslint-plugin-import": "^2.29.0",
"eslint-plugin-prettier": "^5.0.1",
"eslint-plugin-simple-import-sort": "^10.0.0",
"eslint-plugin-unicorn": "^47.0.0",
"eslint-plugin-unicorn": "^48.0.1",
"get-port": "^5.1.1",
"iterator-helpers-polyfill": "^2.3.1",
"jest": "^29.5.0",
"iterator-helpers-polyfill": "^2.3.3",
"jest": "^29.6.4",
"npm-run-all": "^4.1.5",
"prettier": "^2.8.8",
"prettier": "^3.0.3",
"proper-lockfile": "^4.1.2",
"rollup": "^3.26.3",
"rollup": "^4.1.4",
"rollup-plugin-auto-external": "^2.0.0",
"rollup-plugin-dts": "^5.3.0",
"rollup-plugin-dts": "^6.1.0",
"rollup-plugin-node-globals": "^1.4.0",
"rollup-plugin-typescript2": "^0.35.0",
"semantic-release": "^21.0.6",
"semantic-release": "^22.0.5",
"ts-jest": "^29.1.1",
"typedoc": "^0.24.8",
"typescript": "^5.1.6",
"undici": "^5.22.1"
"tslib": "^2.6.2",
"typedoc": "^0.25.2",
"typescript": "^5.2.2",
"undici": "^5.27.0"
},
"files": [
"README.md",
Expand Down
2 changes: 1 addition & 1 deletion rollup.config.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import commonjs from "@rollup/plugin-commonjs";
import json from "@rollup/plugin-json";
import typescript from "@rollup/plugin-typescript";
import autoExternal from "rollup-plugin-auto-external";
import dts from "rollup-plugin-dts";
import typescript from "rollup-plugin-typescript2";

import packageJSON from "./package.json" assert { type: "json" };

Expand Down
2 changes: 2 additions & 0 deletions src/adapters/action/paginator-http.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ export class PaginatorHttp<Entity, Params = undefined>
) => TResult2 | PromiseLike<TResult2> = Promise.reject.bind(Promise),
): Promise<TResult1 | TResult2> {
// we assume the first item won't be undefined
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
return this.next().then((value) => onfulfilled(value.value!), onrejected);
}

Expand All @@ -88,6 +89,7 @@ export class PaginatorHttp<Entity, Params = undefined>
undefined,
Params | string | undefined
> {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return this as any as AsyncIterator<
Entity,
undefined,
Expand Down
1 change: 1 addition & 0 deletions src/adapters/action/proxy.spec.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { type Action } from "../../interfaces";
import { createActionProxy } from "./proxy";

Expand Down
1 change: 1 addition & 0 deletions src/adapters/http/http-native-impl.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ describe("HttpNativeImpl", () => {
);

expect(error.message).toEqual("unknown error");
// eslint-disable-next-line @typescript-eslint/no-explicit-any
expect((error.additionalProperties as any).foo).toEqual("bar");

server.close();
Expand Down
2 changes: 1 addition & 1 deletion src/adapters/http/http-native-impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ export class HttpNativeImpl extends BaseHttp implements Http {
});
const response = await fetch(request);
if (!response.ok) {
// eslint-disable-next-line @typescript-eslint/no-throw-literal
throw response;
}

Expand Down Expand Up @@ -116,6 +115,7 @@ export class HttpNativeImpl extends BaseHttp implements Http {
);
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
if (error != undefined && (error as any).name === "AbortError") {
return new MastoTimeoutError(`Request timed out`, { cause: error });
}
Expand Down
7 changes: 7 additions & 0 deletions src/adapters/ws/web-socket-subscription.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,13 @@ export class WebSocketSubscription implements mastodon.streaming.Subscription {
return this.values();
}

/**
* @experimental This is an experimental API.
*/
[Symbol.dispose](): void {
this.unsubscribe();
}

private matches(event: mastodon.streaming.Event): boolean {
// subscribe("hashtag", { tag: "foo" }) -> ["hashtag", "foo"]
// subscribe("list", { list: "foo" }) -> ["list", "foo"]
Expand Down
5 changes: 5 additions & 0 deletions src/mastodon/streaming/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ export interface Subscription {
unsubscribe(): void;
values(): AsyncIterableIterator<Event>;
[Symbol.asyncIterator](): AsyncIterator<Event, undefined>;

/**
* @experimental This is an experimental API.
*/
[Symbol.dispose](): void;
}

export interface Client {
Expand Down
1 change: 1 addition & 0 deletions test-utils/jest-environment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ class CustomEnvironment extends NodeEnvironment {
await super.setup();
const misc = await this.createGlobals();
this.global.__misc__ = misc;
this.global.Symbol = Symbol;
}

private async createGlobals(): Promise<typeof globalThis.__misc__> {
Expand Down
1 change: 1 addition & 0 deletions test-utils/jest-extend-expect.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-namespace */
export interface CustomMatchers<R = unknown> {
toContainId(id: string): R;
}
Expand Down
1 change: 1 addition & 0 deletions test-utils/jest-global-setup.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-non-null-assertion */
/* eslint-disable unicorn/prefer-module */

import { existsSync } from "node:fs";
Expand Down
61 changes: 1 addition & 60 deletions test-utils/pools/base-pool.ts
Original file line number Diff line number Diff line change
@@ -1,62 +1,3 @@
type UseFn<T, U> = (client: T) => Promise<U>;
type UseFnMany<T, U> = (client: T[]) => Promise<U>;

export interface Pool<T> {
acquire(n?: 1 | undefined): Promise<T>;
acquire(n: number): Promise<T[]>;
release(token: T | T[]): Promise<void>;
use<U>(fn: UseFn<T, U>): Promise<U>;
use<U>(n: number, fn: UseFnMany<T, U>): Promise<U>;
}

export abstract class BasePool<T extends object> implements Pool<T> {
protected abstract acquireOne(): Promise<T>;
protected abstract releaseOne(client: T): Promise<void>;

async acquire(n?: 1 | undefined): Promise<T>;
async acquire(n: number): Promise<T[]>;
async acquire(n = 1): Promise<T | T[]> {
if (n === 1) {
return this.acquireOne();
}
return Promise.all(Array.from({ length: n }).map(() => this.acquireOne()));
}

async release(clients: T | T[]): Promise<void> {
await (Array.isArray(clients)
? Promise.all(clients.map((client) => this.releaseOne(client)))
: this.releaseOne(clients));
}

async use<U>(fn: UseFn<T, U>): Promise<T>;
async use<U>(n: number, fn: UseFnMany<T, U>): Promise<T>;
async use<U>(
fnOrNumber: number | UseFn<T, U>,
fnOrUndefined?: UseFnMany<T, U>,
): Promise<U> {
if (typeof fnOrNumber === "function" && fnOrUndefined == undefined) {
const fn = fnOrNumber;
const client = await this.acquire(1);

try {
return await fn(client);
} finally {
await this.release(client);
}
}

if (typeof fnOrNumber === "number" && typeof fnOrUndefined === "function") {
const n = fnOrNumber;
const fn = fnOrUndefined;
const clients = await this.acquire(n);

try {
return await fn(clients);
} finally {
await this.release(clients);
}
}

throw new Error("Invalid arguments");
}
acquire(): Promise<T>;
}
43 changes: 29 additions & 14 deletions test-utils/pools/session-pool.ts
Original file line number Diff line number Diff line change
@@ -1,38 +1,53 @@
import { type mastodon } from "../../src";
import { createSession, type Session } from "../session";
import { BasePool } from "./base-pool";
import { type Pool } from "./base-pool";
import { type TokenPool } from "./token-pool";

export class SessionPoolImpl extends BasePool<Session> {
export class SessionPoolImpl implements Pool<Session> {
private readonly sessionToToken = new WeakMap<Session, mastodon.v1.Token>();

constructor(
private readonly tokens: TokenPool,
private readonly url: string,
private readonly instance: mastodon.v1.Instance,
) {
super();
}
) {}

protected acquireOne = async (): Promise<Session> => {
acquire = async (): Promise<Session> => {
const token = await this.tokens.acquire();

const session = await createSession(
token,
this.url,
this.instance.urls.streamingApi,
);
try {
const session = await createSession(
token,
this.url,
this.instance.urls.streamingApi,
() => this.release(session),
);

this.sessionToToken.set(session, token);
return session;
// eslint-disable-next-line no-console
console.log(`Acquired session ${session.id} (${session.acct})`);

this.sessionToToken.set(session, token);
return session;
} catch (error) {
await this.tokens.release(token);
throw error;
}
};

protected releaseOne = async (session: Session): Promise<void> => {
release = async (session: Session): Promise<void> => {
session.ws.close();
const token = this.sessionToToken.get(session);
if (token == undefined) {
// eslint-disable-next-line no-console
console.warn(
`Session ${session.id} (${session.acct}) is already released`,
);
return;
}

// eslint-disable-next-line no-console
console.log(`Released session ${session.id} (${session.acct})`);

await this.tokens.release(token);
this.sessionToToken.delete(session);
};
Expand Down
Loading

0 comments on commit 6ad8afa

Please sign in to comment.