Skip to content

Commit

Permalink
feat: add @discordjs/util (#8591)
Browse files Browse the repository at this point in the history
* feat: add @discordjs/util

* fix: builders test

* refactor: make rest use lazy for ESM import

* chore: make requested changes

* Apply suggestions from code review

Co-authored-by: Parbez <imranbarbhuiya.fsd@gmail.com>
Co-authored-by: A. Román <kyradiscord@gmail.com>

* chore: make requested changes and add tests

* chore: regen lockfile

* test: add type tests

* chore: push missing files

* chore: make requested changes

* chore: update CI stuff

* chore: fix lockfile

* chore: make requested changes

Co-authored-by: Parbez <imranbarbhuiya.fsd@gmail.com>
Co-authored-by: A. Román <kyradiscord@gmail.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
  • Loading branch information
4 people committed Oct 2, 2022
1 parent 3f86561 commit b2ec865
Show file tree
Hide file tree
Showing 56 changed files with 1,304 additions and 1,319 deletions.
4 changes: 4 additions & 0 deletions .github/labeler.yml
Expand Up @@ -37,3 +37,7 @@
'packages:ws':
- packages/ws/*
- packages/ws/**/*

'packages:util':
- packages/util/*
- packages/util/**/*
2 changes: 2 additions & 0 deletions .github/labels.yml
Expand Up @@ -66,6 +66,8 @@
color: 'fbca04'
- name: 'packages:ws'
color: 'fbca04'
- name: 'packages:util'
color: 'fbca04'
- name: 'performance'
color: '80c042'
- name: 'permissions'
Expand Down
6 changes: 6 additions & 0 deletions packages/actions/src/uploadCoverage/action.yml
Expand Up @@ -51,6 +51,12 @@ runs:
files: ./packages/ws/coverage/cobertura-coverage.xml
flags: ws

- name: Upload Util Coverage
uses: codecov/codecov-action@v3
with:
files: ./packages/util/coverage/cobertura-coverage.xml
flags: util

- name: Upload Utilities Coverage
uses: codecov/codecov-action@v3
with:
Expand Down
30 changes: 1 addition & 29 deletions packages/builders/__tests__/util.test.ts
@@ -1,33 +1,5 @@
import { describe, test, expect } from 'vitest';
import {
isJSONEncodable,
isEquatable,
ActionRowBuilder,
enableValidators,
disableValidators,
isValidationEnabled,
} from '../src/index.js';

describe('isEquatable', () => {
test('returns true if the object is equatable', () => {
expect(isEquatable({ equals: () => true })).toBeTruthy();
});

test('returns false if the object is not equatable', () => {
expect(isEquatable({})).toBeFalsy();
});
});

describe('isJSONEncodable', () => {
test('returns true if the object is JSON encodable', () => {
expect(isJSONEncodable({ toJSON: () => ({}) })).toBeTruthy();
expect(isJSONEncodable(new ActionRowBuilder())).toBeTruthy();
});

test('returns false if the object is not JSON encodable', () => {
expect(isJSONEncodable({})).toBeFalsy();
});
});
import { enableValidators, disableValidators, isValidationEnabled } from '../src/index.js';

describe('validation', () => {
test('enables validation', () => {
Expand Down
1 change: 1 addition & 0 deletions packages/builders/package.json
Expand Up @@ -54,6 +54,7 @@
},
"homepage": "https://discord.js.org",
"dependencies": {
"@discordjs/util": "workspace:^",
"@sapphire/shapeshift": "^3.6.0",
"discord-api-types": "^0.37.11",
"fast-deep-equal": "^3.1.3",
Expand Down
2 changes: 1 addition & 1 deletion packages/builders/src/components/Component.ts
@@ -1,10 +1,10 @@
import type { JSONEncodable } from '@discordjs/util';
import type {
APIActionRowComponent,
APIActionRowComponentTypes,
APIBaseComponent,
ComponentType,
} from 'discord-api-types/v10';
import type { JSONEncodable } from '../util/jsonEncodable';

export type AnyAPIActionRowComponent = APIActionRowComponent<APIActionRowComponentTypes> | APIActionRowComponentTypes;

Expand Down
@@ -1,5 +1,5 @@
import type { JSONEncodable } from '@discordjs/util';
import type { APIMessageComponentEmoji, APISelectMenuOption } from 'discord-api-types/v10';
import type { JSONEncodable } from '../../util/jsonEncodable.js';
import {
defaultValidator,
emojiValidator,
Expand Down
3 changes: 1 addition & 2 deletions packages/builders/src/components/textInput/TextInput.ts
@@ -1,7 +1,6 @@
import { isJSONEncodable, type Equatable, type JSONEncodable } from '@discordjs/util';
import { ComponentType, type TextInputStyle, type APITextInputComponent } from 'discord-api-types/v10';
import isEqual from 'fast-deep-equal';
import type { Equatable } from '../../util/equatable';
import { isJSONEncodable, type JSONEncodable } from '../../util/jsonEncodable.js';
import { customIdValidator } from '../Assertions.js';
import { ComponentBuilder } from '../Component.js';
import {
Expand Down
3 changes: 1 addition & 2 deletions packages/builders/src/index.ts
Expand Up @@ -36,8 +36,7 @@ export * from './interactions/slashCommands/mixins/SharedSlashCommandOptions.js'
export * as ContextMenuCommandAssertions from './interactions/contextMenuCommands/Assertions.js';
export * from './interactions/contextMenuCommands/ContextMenuCommandBuilder.js';

export * from './util/jsonEncodable.js';
export * from './util/equatable.js';
export * from './util/componentUtil.js';
export * from './util/normalizeArray.js';
export * from './util/validation.js';
export * from '@discordjs/util';
2 changes: 1 addition & 1 deletion packages/builders/src/interactions/modals/Modal.ts
@@ -1,3 +1,4 @@
import type { JSONEncodable } from '@discordjs/util';
import type {
APIActionRowComponent,
APIModalActionRowComponent,
Expand All @@ -6,7 +7,6 @@ import type {
import { ActionRowBuilder, type ModalActionRowComponentBuilder } from '../../components/ActionRow.js';
import { customIdValidator } from '../../components/Assertions.js';
import { createComponentBuilder } from '../../components/Components.js';
import type { JSONEncodable } from '../../util/jsonEncodable';
import { normalizeArray, type RestOrArray } from '../../util/normalizeArray.js';
import { titleValidator, validateRequiredParameters } from './Assertions.js';

Expand Down
1 change: 1 addition & 0 deletions packages/discord.js/package.json
Expand Up @@ -52,6 +52,7 @@
"@discordjs/builders": "workspace:^",
"@discordjs/collection": "workspace:^",
"@discordjs/rest": "workspace:^",
"@discordjs/util": "workspace:^",
"@sapphire/snowflake": "^3.2.2",
"@types/ws": "^8.5.3",
"discord-api-types": "^0.37.11",
Expand Down
@@ -1,9 +1,9 @@
'use strict';

const { lazy } = require('@discordjs/util');
const { ApplicationCommandOptionType } = require('discord-api-types/v10');
const CommandInteraction = require('./CommandInteraction');
const CommandInteractionOptionResolver = require('./CommandInteractionOptionResolver');
const { lazy } = require('../util/Util');

const getMessage = lazy(() => require('./Message').Message);

Expand Down
@@ -1,9 +1,9 @@
'use strict';

const { lazy } = require('@discordjs/util');
const BaseInteraction = require('./BaseInteraction');
const InteractionWebhook = require('./InteractionWebhook');
const InteractionResponses = require('./interfaces/InteractionResponses');
const { lazy } = require('../util/Util');

const getMessage = lazy(() => require('./Message').Message);

Expand Down
3 changes: 2 additions & 1 deletion packages/discord.js/src/structures/MessagePayload.js
Expand Up @@ -2,12 +2,13 @@

const { Buffer } = require('node:buffer');
const { isJSONEncodable } = require('@discordjs/builders');
const { lazy } = require('@discordjs/util');
const { MessageFlags } = require('discord-api-types/v10');
const ActionRowBuilder = require('./ActionRowBuilder');
const { RangeError, ErrorCodes } = require('../errors');
const DataResolver = require('../util/DataResolver');
const MessageFlagsBitField = require('../util/MessageFlagsBitField');
const { basename, verifyString, lazy } = require('../util/Util');
const { basename, verifyString } = require('../util/Util');

const getBaseInteraction = lazy(() => require('./BaseInteraction'));

Expand Down
@@ -1,10 +1,10 @@
'use strict';

const { lazy } = require('@discordjs/util');
const BaseInteraction = require('./BaseInteraction');
const InteractionWebhook = require('./InteractionWebhook');
const ModalSubmitFields = require('./ModalSubmitFields');
const InteractionResponses = require('./interfaces/InteractionResponses');
const { lazy } = require('../util/Util');

const getMessage = lazy(() => require('./Message').Message);

Expand Down
2 changes: 1 addition & 1 deletion packages/discord.js/src/structures/Webhook.js
@@ -1,12 +1,12 @@
'use strict';

const { makeURLSearchParams } = require('@discordjs/rest');
const { lazy } = require('@discordjs/util');
const { DiscordSnowflake } = require('@sapphire/snowflake');
const { Routes, WebhookType } = require('discord-api-types/v10');
const MessagePayload = require('./MessagePayload');
const { Error, ErrorCodes } = require('../errors');
const DataResolver = require('../util/DataResolver');
const { lazy } = require('../util/Util');

const getMessage = lazy(() => require('./Message').Message);

Expand Down
2 changes: 1 addition & 1 deletion packages/discord.js/src/util/Channels.js
@@ -1,7 +1,7 @@
'use strict';

const { lazy } = require('@discordjs/util');
const { ChannelType } = require('discord-api-types/v10');
const { lazy } = require('./Util');

const getCategoryChannel = lazy(() => require('../structures/CategoryChannel'));
const getDMChannel = lazy(() => require('../structures/DMChannel'));
Expand Down
11 changes: 0 additions & 11 deletions packages/discord.js/src/util/Util.js
Expand Up @@ -509,16 +509,6 @@ function cleanCodeBlockContent(text) {
return text.replaceAll('```', '`\u200b``');
}

/**
* Lazily evaluates a callback function
* @param {Function} cb The callback to lazily evaluate
* @returns {Function}
*/
function lazy(cb) {
let defaultValue;
return () => (defaultValue ??= cb());
}

/**
* Parses a webhook URL for the id and token.
* @param {string} url The URL to parse
Expand Down Expand Up @@ -562,7 +552,6 @@ module.exports = {
basename,
cleanContent,
cleanCodeBlockContent,
lazy,
parseWebhookURL,
};

Expand Down
5 changes: 2 additions & 3 deletions packages/discord.js/typings/index.d.ts
Expand Up @@ -12,7 +12,6 @@ import {
hyperlink,
inlineCode,
italic,
JSONEncodable,
quote,
roleMention,
SelectMenuBuilder as BuilderSelectMenuComponent,
Expand All @@ -31,6 +30,7 @@ import {
ComponentBuilder,
type RestOrArray,
} from '@discordjs/builders';
import { Awaitable, JSONEncodable } from '@discordjs/util';
import { Collection } from '@discordjs/collection';
import { BaseImageURLOptions, ImageURLOptions, RawFile, REST, RESTOptions } from '@discordjs/rest';
import {
Expand Down Expand Up @@ -4144,8 +4144,6 @@ export interface AuditLogChange {
new?: APIAuditLogChange['new_value'];
}

export type Awaitable<T> = T | PromiseLike<T>;

export type AwaitMessageComponentOptions<T extends CollectedMessageInteraction> = Omit<
MessageComponentCollectorOptions<T>,
'max' | 'maxComponents' | 'maxUsers'
Expand Down Expand Up @@ -5835,3 +5833,4 @@ export type InternalDiscordGatewayAdapterCreator = (
export * from 'discord-api-types/v10';
export * from '@discordjs/builders';
export * from '@discordjs/rest';
export * from '@discordjs/util';
1 change: 1 addition & 0 deletions packages/proxy/package.json
Expand Up @@ -55,6 +55,7 @@
"homepage": "https://discord.js.org",
"dependencies": {
"@discordjs/rest": "^1.0.0",
"@discordjs/util": "workspace:^",
"tslib": "^2.4.0",
"undici": "^5.10.0"
},
Expand Down
6 changes: 1 addition & 5 deletions packages/proxy/src/util/util.ts
@@ -1,9 +1,5 @@
import type { IncomingMessage, ServerResponse } from 'node:http';

/**
* Represents a potentially awaitable value
*/
export type Awaitable<T> = PromiseLike<T> | T;
import type { Awaitable } from '@discordjs/util';

/**
* Represents a simple HTTP request handler
Expand Down
1 change: 1 addition & 0 deletions packages/rest/package.json
Expand Up @@ -53,6 +53,7 @@
"homepage": "https://discord.js.org",
"dependencies": {
"@discordjs/collection": "workspace:^",
"@discordjs/util": "workspace:^",
"@sapphire/async-queue": "^1.5.0",
"@sapphire/snowflake": "^3.2.2",
"discord-api-types": "^0.37.11",
Expand Down
8 changes: 2 additions & 6 deletions packages/rest/src/lib/RequestManager.ts
Expand Up @@ -3,6 +3,7 @@ import { EventEmitter } from 'node:events';
import { setInterval, clearInterval } from 'node:timers';
import type { URLSearchParams } from 'node:url';
import { Collection } from '@discordjs/collection';
import { lazy } from '@discordjs/util';
import { DiscordSnowflake } from '@sapphire/snowflake';
import { FormData, type RequestInit, type BodyInit, type Dispatcher, type Agent } from 'undici';
import type { RESTOptions, RestEvents, RequestOptions } from './REST.js';
Expand All @@ -12,12 +13,7 @@ import { DefaultRestOptions, DefaultUserAgent, RESTEvents } from './utils/consta
import { resolveBody } from './utils/utils.js';

// Make this a lazy dynamic import as file-type is a pure ESM package
// eslint-disable-next-line @typescript-eslint/consistent-type-imports
const getFileType = async (): Promise<typeof import('file-type')> => {
// eslint-disable-next-line @typescript-eslint/consistent-type-imports
let cached: Promise<typeof import('file-type')>;
return (cached ??= import('file-type'));
};
const getFileType = lazy(async () => import('file-type'));

/**
* Represents a file to be added to the request
Expand Down
3 changes: 3 additions & 0 deletions packages/util/.eslintrc.json
@@ -0,0 +1,3 @@
{
"extends": "../../.eslintrc.json"
}
25 changes: 25 additions & 0 deletions packages/util/.gitignore
@@ -0,0 +1,25 @@
# Packages
node_modules/

# Log files
logs/
*.log
npm-debug.log*

# Runtime data
pids
*.pid
*.seed

# Env
.env

# Dist
dist/
typings/

docs/**/*

# Miscellaneous
.tmp/
coverage/
1 change: 1 addition & 0 deletions packages/util/.lintstagedrc.js
@@ -0,0 +1 @@
module.exports = require('../../.lintstagedrc.json');
8 changes: 8 additions & 0 deletions packages/util/.prettierignore
@@ -0,0 +1,8 @@
# Autogenerated
CHANGELOG.md
.turbo
dist/
docs/**/*
!docs/index.yml
!docs/README.md
coverage/
1 change: 1 addition & 0 deletions packages/util/.prettierrc.js
@@ -0,0 +1 @@
module.exports = require('../../.prettierrc.json');

0 comments on commit b2ec865

Please sign in to comment.