Skip to content

Commit

Permalink
feat: Move entities and repositories under the namespace
Browse files Browse the repository at this point in the history
BREAKING CHANGE: You have to import `mastodon` to use any entity instead of importing single entity directly
  • Loading branch information
neet committed Dec 23, 2022
1 parent b63621b commit 5da5773
Show file tree
Hide file tree
Showing 118 changed files with 124 additions and 98 deletions.
59 changes: 34 additions & 25 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,40 +2,49 @@

Thank you for considering contribution. Please check following guideline and please make sure that you satisfy the policy.

## Project setup
## Setup

1. Install **Node.js**, **Git** and **Yarn**.
2. Clone this repository by `git clone`
3. Run `yarn --pure-lockfile` to install dependencies
3. Run `yarn install` to install dependencies

## Project Structure

Our project in organized under the following directory structure.

```
.
├── package.json
├── src
│ ├── clients # Client interfaces. these are basically facade classes for the repositories
│ ├── config.ts # Configuration interface passed to login()
│ ├── decorators # Decorators used in repositories
│ ├── entities # Public API response types
│ │ └── admin # Admin API response types
│ ├── entrypoints
│ │ ├── fetch.ts
│ │ └── nodejs.ts # This will be the `index.js` field of `masto` module.
│ ├── errors # Custom error classes
│ ├── http # HTTP wrappers. Classes here are basically an abstraction of fetch API or axios.
│ ├── paginator.ts # An abstraction of pagination
│ ├── repositories # API definitions. Used by ./clients/masto.ts
│ │ └── admin # Admin API definitions. Used by ./clients/admin.ts
│ ├── serializers # Classes that transform requests and responses
│ ├── utils # General utilities
│ └── ws # WebSocket wrappers.
├── test-utils
├── tests
├── tsconfig.json
└── yarn.lock
./src
├── mastodon TypeScript representation of Mastodon API.
│ │ This directory is public under the namespace `mastodon` and does not contain library-specific code
│ ├── v1
│ │ ├── entities V1 response types
│ │ └── repositories V1 resource classes
│ └── v2
│ ├── entities V2 response types
│ └── repositories V2 resource classes
├── errors Error classes
├── http HTTP wrapper
├── logger Logging service
├── serializers Service to encode requests or decode responses
├── utils General utilities
└── ws Websocket wrapper
```

### Repository

_Repository_ is a class for representing REST resources. They have several methods and multiple implementations, all named according to the following convention. Let `x` is the name of a resource.

| URL Pattern | Method Name | Parameter Name |
| ----------------------------- | ------------------ | -------------------------------- |
| `GET /` | `list` | `ListResourceParams` |
| `GET /:id` | `fetch` | `FetchResourceParams` |
| `POST /` | `create` | `CreateResourceParams` |
| `POST /:id/{verb}` | `do` (verb) | `DoResourceParams` |
| `GET /:id/{subresource}` | `listSubresource` | `ListResourceSubresourceParams` |
| `GET /:id/{subresource}/:id2` | `fetchSubresource` | `FetchResourceSubresourceParams` |
| `DELETE /:id` | `remove` | `RemoveResourceParams` |
| `PUT /:id` or `PATCH /:id` | `update` | `UpdateResourceParams` |

## Scripts

There are some useful scripts in `package.json` which you can with `yarn run`.
Expand Down
1 change: 1 addition & 0 deletions cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
"serializers",
"shortcode",
"subprotocol",
"subresource",
"typedoc",
"unassign",
"unbookmark",
Expand Down
5 changes: 4 additions & 1 deletion examples/create-new-status.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import type { mastodon } from 'masto';
import { login } from 'masto';

const masto = await login({
url: 'https://example.com',
accessToken: 'YOUR TOKEN',
});

await masto.v1.statuses.create({
const s: mastodon.v1.Status = await masto.v1.statuses.create({
status: 'Hello from #mastojs!',
visibility: 'public',
});

console.log(s);
4 changes: 2 additions & 2 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import { LogLevel } from './logger';
import type { Serializer } from './serializers';
import { mergeAbortSignals, mergeHeadersInit } from './utils';

export type VersionCompat = 'unimplemented' | 'removed' | 'compatible';
export type SatisfiesVersionRangeResult = {
type VersionCompat = 'unimplemented' | 'removed' | 'compatible';
type SatisfiesVersionRangeResult = {
compat: VersionCompat;
version?: string;
};
Expand Down
52 changes: 4 additions & 48 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,52 +1,8 @@
import { SemVer } from 'semver';

import { MastoClient } from './api';
import { InstanceRepository } from './api/v1/repositories';
import type { MastoConfigProps } from './config';
import { MastoConfig } from './config';
import { HttpNativeImpl } from './http';
import type { LogType } from './logger';
import { LoggerConsoleImpl } from './logger';
import { SerializerNativeImpl } from './serializers';
import type { Writable } from './utils/writable';
import { WsNativeImpl } from './ws';

export type LoginParams = Omit<
MastoConfigProps,
'streamingApiUrl' | 'version' | 'logLevel'
> & { logLevel?: LogType };

export const login = async (params: LoginParams): Promise<MastoClient> => {
const draft: Writable<MastoConfigProps> = {
streamingApiUrl: '',
...params,
};
const serializer = new SerializerNativeImpl();

{
const config = new MastoConfig(draft, serializer);
const http = new HttpNativeImpl(serializer, config);
const instance = await new InstanceRepository(http, config).fetch();
draft.version = new SemVer(instance.version);
draft.streamingApiUrl = instance.urls.streamingApi;
}

const config = new MastoConfig(draft, serializer);
const logger = new LoggerConsoleImpl(config.getLogLevel());
const ws = new WsNativeImpl(config, serializer);
const http = new HttpNativeImpl(serializer, config, logger);

logger.debug('Masto.js initialised', config);

return new MastoClient(http, ws, config);
};

export * from './api';
export * from './decorators';
export * from './errors';
export * from './config';
export * from './http';
export * from './logger';
export * from './login';
export * as mastodon from './mastodon';
export * from './paginator';
export * from './serializers';
export * from './ws';
export * from './config';
export * from './paginator';
57 changes: 57 additions & 0 deletions src/login.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import type { RequestInit } from '@mastojs/ponyfills';
import { SemVer } from 'semver';

import type { MastoConfigProps } from './config';
import { MastoConfig } from './config';
import { HttpNativeImpl } from './http';
import type { LogType } from './logger';
import { LoggerConsoleImpl } from './logger';
import { Client } from './mastodon';
import { InstanceRepository } from './mastodon/v2/repositories';
import { SerializerNativeImpl } from './serializers';
import type { Writable } from './utils';
import { WsNativeImpl } from './ws';

export type LoginParams = {
/** URL of your instance */
readonly url: string;
/** Log level to print to your console */
readonly logLevel?: LogType;
/** Access token of your account */
readonly accessToken?: string;
/** Timeout in milliseconds */
readonly timeout?: number;
readonly defaultRequestInit?: Omit<RequestInit, 'body' | 'method'>;
readonly disableVersionCheck?: boolean;
readonly disableDeprecatedWarning?: boolean;
};

export const login = async (params: LoginParams): Promise<Client> => {
const draft: Writable<MastoConfigProps> = {
url: params.url,
streamingApiUrl: '',
logLevel: params.logLevel,
accessToken: params.accessToken,
timeout: params.timeout,
defaultRequestInit: params.defaultRequestInit,
disableVersionCheck: params.disableVersionCheck,
disableDeprecatedWarning: params.disableDeprecatedWarning,
};
const serializer = new SerializerNativeImpl();

{
const config = new MastoConfig(draft, serializer);
const http = new HttpNativeImpl(serializer, config);
const instance = await new InstanceRepository(http, config).fetch();
draft.version = new SemVer(instance.version);
draft.streamingApiUrl = instance.configuration.urls.streamingApi;
}

const config = new MastoConfig(draft, serializer);
const logger = new LoggerConsoleImpl(config.getLogLevel());
const ws = new WsNativeImpl(config, serializer);
const http = new HttpNativeImpl(serializer, config, logger);

logger.debug('Masto.js initialised', config);
return new Client(http, ws, config);
};
2 changes: 1 addition & 1 deletion src/api/client.ts → src/mastodon/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import type { Ws } from '../ws';
import { AggregateRepository as V1AggregateRepository } from './v1';
import { AggregateRepository as V2AggregateRepository } from './v2';

export class MastoClient {
export class Client {
readonly v1: V1AggregateRepository;
readonly v2: V2AggregateRepository;

Expand Down
4 changes: 2 additions & 2 deletions src/api/index.ts → src/mastodon/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export * as V1 from './v1';
export * as V2 from './v2';
export * as v1 from './v1';
export * as v2 from './v2';
export * from './repository';
export * from './client';
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { V1 } from '../..';
import type { v1 } from '../..';

export interface InstanceUsageUsers {
/** The number of active users in the past 4 weeks. */
Expand Down Expand Up @@ -104,7 +104,7 @@ export interface InstanceContact {
/** An email address that can be messaged regarding inquiries or issues. */
email: string;
/** An account that can be contacted natively over the network regarding inquiries or issues. */
account: V1.Account;
account: v1.Account;
}

/**
Expand Down Expand Up @@ -135,5 +135,5 @@ export interface Instance {
/** Hints related to contacting a representative of the website. */
contact: InstanceContact;
/** An itemized list of rules for this website. */
rules: V1.Rule[];
rules: v1.Rule[];
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import type { V1 } from '../..';
import type { v1 } from '../..';

/**
* Represents the results of a search.
* @see https://docs.joinmastodon.org/entities/Search/
*/
export interface Search {
/** Accounts which match the given query */
accounts: V1.Account[];
accounts: v1.Account[];
/** Statuses which match the given query */
statuses: V1.Status[];
statuses: v1.Status[];
/** Hashtags which match the given query */
hashtags: V1.Tag[];
hashtags: v1.Tag[];
}
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { version } from '../../../decorators';
import type { Http } from '../../../http';
import type { Logger } from '../../../logger';
import { Paginator } from '../../../paginator';
import type { V1 } from '../..';
import type { v1 } from '../..';
import type { Repository } from '../../repository';

export interface ListSuggestionsParams {
Expand All @@ -13,7 +13,7 @@ export interface ListSuggestionsParams {

export class SuggestionRepository
implements
Repository<V1.Suggestion, never, never, never, ListSuggestionsParams>
Repository<v1.Suggestion, never, never, never, ListSuggestionsParams>
{
constructor(
private readonly http: Http,
Expand All @@ -30,7 +30,7 @@ export class SuggestionRepository
@version({ since: '3.4.0' })
list(
params?: ListSuggestionsParams,
): Paginator<V1.Suggestion[], ListSuggestionsParams> {
): Paginator<v1.Suggestion[], ListSuggestionsParams> {
return new Paginator(this.http, '/api/v2/suggestions', params);
}
}
2 changes: 1 addition & 1 deletion src/ws/ws.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import type {
Notification,
Reaction,
Status,
} from '../api/v1/entities';
} from '../mastodon/v1/entities';

/**
* Map of event name and callback argument
Expand Down
4 changes: 2 additions & 2 deletions test-utils/login.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import type { MastoClient } from '../src';
import type { mastodon } from '../src';
import { login as originalLogin } from '../src';

interface Options {
unauthenticated?: boolean;
}

export const login = (options?: Options): Promise<MastoClient> => {
export const login = (options?: Options): Promise<mastodon.Client> => {
const unauthenticated = options?.unauthenticated;

return originalLogin({
Expand Down
4 changes: 2 additions & 2 deletions tests/accounts.spec.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import type { MastoClient } from '../src/api';
import type { mastodon } from '../src';
import { login } from '../test-utils/login';

describe('account', () => {
let client: MastoClient;
let client: mastodon.Client;
const TARGET_ID = process.env.TEST_TARGET_ID ?? '200896';

beforeAll(async () => {
Expand Down
4 changes: 2 additions & 2 deletions tests/media.spec.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import fs from 'node:fs/promises';
import path from 'node:path';

import type { MastoClient } from '../src/api';
import type { mastodon } from '../src';
import { login } from '../test-utils/login';

describe('account', () => {
let client: MastoClient;
let client: mastodon.Client;

beforeAll(async () => {
client = await login();
Expand Down
4 changes: 2 additions & 2 deletions tests/statuses.spec.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import type { MastoClient } from '../src/api';
import type { mastodon } from '../src';
import { login } from '../test-utils/login';

describe('account', () => {
let client: MastoClient;
let client: mastodon.Client;

beforeAll(async () => {
client = await login();
Expand Down

0 comments on commit 5da5773

Please sign in to comment.