chapterjs 0.1.4 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/build.js +9 -10
- package/dist/cli/dev.js +17 -16
- package/dist/cli/project.d.ts +38 -25
- package/dist/cli/project.js +199 -134
- package/dist/commands/command.d.ts +25 -11
- package/dist/commands/command.js +5 -5
- package/dist/commands/{convention.d.ts → declaration.d.ts} +24 -11
- package/dist/commands/{convention.js → declaration.js} +61 -33
- package/dist/commands/tree.d.ts +8 -4
- package/dist/commands/tree.js +16 -6
- package/dist/components/button.d.ts +13 -13
- package/dist/components/button.js +9 -11
- package/dist/components/component.d.ts +1 -1
- package/dist/components/component.js +1 -1
- package/dist/components/custom-id.d.ts +4 -4
- package/dist/components/custom-id.js +12 -12
- package/dist/components/declaration.d.ts +42 -0
- package/dist/components/{convention.js → declaration.js} +76 -73
- package/dist/components/declared.d.ts +29 -0
- package/dist/components/declared.js +49 -0
- package/dist/components/embed.d.ts +13 -12
- package/dist/components/embed.js +6 -6
- package/dist/components/instance.d.ts +3 -3
- package/dist/components/instance.js +2 -2
- package/dist/components/modal.d.ts +11 -10
- package/dist/components/modal.js +7 -8
- package/dist/components/router.d.ts +3 -2
- package/dist/components/router.js +14 -19
- package/dist/components/select.d.ts +14 -12
- package/dist/components/select.js +4 -4
- package/dist/events/declaration.d.ts +16 -0
- package/dist/events/declaration.js +80 -0
- package/dist/events/event.d.ts +42 -5
- package/dist/events/event.js +25 -11
- package/dist/events/registry.d.ts +1 -10
- package/dist/events/router.d.ts +3 -2
- package/dist/index.d.ts +11 -9
- package/dist/index.js +2 -0
- package/dist/loader/generated.d.ts +5 -32
- package/dist/loader/generated.js +21 -55
- package/dist/loader/loader.d.ts +57 -44
- package/dist/loader/loader.js +73 -89
- package/dist/main.d.ts +0 -1
- package/dist/main.js +2 -3
- package/dist/messages/commands.d.ts +3 -3
- package/dist/messages/commands.js +29 -10
- package/dist/messages/declaration.d.ts +89 -0
- package/dist/messages/declaration.js +455 -0
- package/dist/messages/messages.d.ts +65 -43
- package/dist/messages/messages.js +11 -10
- package/dist/messages/phrases.js +1 -1
- package/dist/messages/translate.d.ts +4 -4
- package/dist/messages/translate.js +4 -4
- package/dist/presence/declaration.d.ts +19 -0
- package/dist/presence/{convention.js → declaration.js} +13 -28
- package/dist/presence/presence.d.ts +7 -7
- package/dist/presence/presence.js +5 -5
- package/dist/structures/context.d.ts +1 -1
- package/dist/structures/interaction.d.ts +2 -2
- package/dist/structures/interaction.js +3 -3
- package/dist/structures/payload.d.ts +4 -4
- package/dist/structures/payload.js +4 -4
- package/dist/tasks/declaration.d.ts +16 -0
- package/dist/tasks/{convention.js → declaration.js} +14 -36
- package/dist/tasks/scheduler.d.ts +3 -1
- package/dist/tasks/scheduler.js +4 -4
- package/dist/tasks/task.d.ts +8 -9
- package/dist/tasks/task.js +6 -7
- package/package.json +1 -1
- package/dist/components/convention.d.ts +0 -35
- package/dist/components/file.d.ts +0 -30
- package/dist/components/file.js +0 -52
- package/dist/events/convention.d.ts +0 -25
- package/dist/events/convention.js +0 -97
- package/dist/events/types.d.ts +0 -7
- package/dist/events/types.js +0 -48
- package/dist/messages/convention.d.ts +0 -89
- package/dist/messages/convention.js +0 -347
- package/dist/presence/convention.d.ts +0 -26
- package/dist/tasks/convention.d.ts +0 -26
package/dist/messages/phrases.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// What the bot answers by itself, when it can't run what was asked: in
|
|
2
|
-
// English here, and in the
|
|
2
|
+
// English here, and in the language files that translate
|
|
3
3
|
// them (the `framework` of a language file).
|
|
4
4
|
import { fill, pickLocale, templateOf } from './translate.js';
|
|
5
5
|
/** The phrases of the framework, in English. */
|
|
@@ -18,7 +18,7 @@ export interface PluralTemplate {
|
|
|
18
18
|
readonly params: readonly string[];
|
|
19
19
|
}
|
|
20
20
|
export type Template = TextTemplate | PluralTemplate;
|
|
21
|
-
/** The languages of
|
|
21
|
+
/** The languages of the project, checked and assembled: what `t` reads. */
|
|
22
22
|
export interface LoadedMessages {
|
|
23
23
|
readonly default: Locale;
|
|
24
24
|
/** The texts of each language declared, compiled. */
|
|
@@ -31,7 +31,7 @@ export interface LoadedMessages {
|
|
|
31
31
|
* needed for that.
|
|
32
32
|
*/
|
|
33
33
|
readonly commands: ReadonlyMap<Locale, Readonly<Record<string, unknown>>>;
|
|
34
|
-
/** The file
|
|
34
|
+
/** The file each language is declared in. */
|
|
35
35
|
readonly files: ReadonlyMap<Locale, string>;
|
|
36
36
|
}
|
|
37
37
|
/** The forms a plural can have, in the order of the docs. */
|
|
@@ -82,7 +82,7 @@ export interface Audience {
|
|
|
82
82
|
* @see https://docs.discord.com/developers/resources/guild#guild-object-guild-structure
|
|
83
83
|
*/
|
|
84
84
|
export declare function audienceLocale({ person, guild, ephemeral, }: Audience): string | null;
|
|
85
|
-
/** A `t` for a project without a language
|
|
85
|
+
/** A `t` for a project without a language: it says so when called. */
|
|
86
86
|
export declare function missingTranslator(locale: string | null): Translator<MessageTexts>;
|
|
87
87
|
/**
|
|
88
88
|
* `t` for what the bot sends, in the language of who will read it. Without
|
|
@@ -92,7 +92,7 @@ export declare function missingTranslator(locale: string | null): Translator<Mes
|
|
|
92
92
|
export declare function translatorOf(ctx: Pick<Context, 'messages'>, audience: Audience): Translator<MessageTexts>;
|
|
93
93
|
/**
|
|
94
94
|
* What a handler receives about translations: `t` in the language asked
|
|
95
|
-
* when the project has a language
|
|
95
|
+
* when the project has a language, nothing otherwise. Spread it
|
|
96
96
|
* into the context of any handler.
|
|
97
97
|
*/
|
|
98
98
|
export declare function translation(ctx: Pick<Context, 'messages'>, locale: string | null): {
|
|
@@ -176,7 +176,7 @@ export function translatorFor(messages, locale) {
|
|
|
176
176
|
return fill(key, own, params, picked);
|
|
177
177
|
const template = base.get(key);
|
|
178
178
|
if (!template) {
|
|
179
|
-
throw new TypeError(`There is no message "${key}" in
|
|
179
|
+
throw new TypeError(`There is no message "${key}" in your languages. They have: ${[...base.keys()].join(', ')}.`);
|
|
180
180
|
}
|
|
181
181
|
return fill(key, template, params, messages.default);
|
|
182
182
|
});
|
|
@@ -208,14 +208,14 @@ export function audienceLocale({ person, guild, ephemeral, }) {
|
|
|
208
208
|
return guild.features.includes('COMMUNITY') ? guild.preferredLocale : null;
|
|
209
209
|
}
|
|
210
210
|
const missing = new Map();
|
|
211
|
-
/** A `t` for a project without a language
|
|
211
|
+
/** A `t` for a project without a language: it says so when called. */
|
|
212
212
|
export function missingTranslator(locale) {
|
|
213
213
|
const shown = locale ?? 'en-US';
|
|
214
214
|
const known = missing.get(shown);
|
|
215
215
|
if (known)
|
|
216
216
|
return known;
|
|
217
217
|
const t = (() => {
|
|
218
|
-
throw new TypeError(
|
|
218
|
+
throw new TypeError("This project has no language: declare one with language({ locale: 'en-US', texts: { ... } }) to use t().");
|
|
219
219
|
});
|
|
220
220
|
Object.defineProperties(t, {
|
|
221
221
|
locale: { value: shown, enumerable: true },
|
|
@@ -241,7 +241,7 @@ export function translatorOf(ctx, audience) {
|
|
|
241
241
|
}
|
|
242
242
|
/**
|
|
243
243
|
* What a handler receives about translations: `t` in the language asked
|
|
244
|
-
* when the project has a language
|
|
244
|
+
* when the project has a language, nothing otherwise. Spread it
|
|
245
245
|
* into the context of any handler.
|
|
246
246
|
*/
|
|
247
247
|
export function translation(ctx, locale) {
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { Declaration } from '../loader/loader.js';
|
|
2
|
+
import { type RawGatewayPresenceUpdate } from '../discord/types/gateway-events.js';
|
|
3
|
+
/** The presence, checked: what the gateway sends. */
|
|
4
|
+
export interface LoadedPresence {
|
|
5
|
+
/** The Update Presence payload (also sent with Identify). */
|
|
6
|
+
readonly raw: RawGatewayPresenceUpdate;
|
|
7
|
+
/** What tells one presence from another: the payload, as text. */
|
|
8
|
+
readonly key: string;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* What a bot without a presence shows: online, doing nothing. Sent when
|
|
12
|
+
* the declaration is removed while the bot runs.
|
|
13
|
+
*/
|
|
14
|
+
export declare const DEFAULT_PRESENCE: RawGatewayPresenceUpdate;
|
|
15
|
+
/**
|
|
16
|
+
* The presence of the bot, declared once anywhere in `src/` with
|
|
17
|
+
* `presence()` and exported.
|
|
18
|
+
*/
|
|
19
|
+
export declare const presenceDeclaration: Declaration<LoadedPresence>;
|
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
//
|
|
2
|
-
// name.
|
|
3
|
-
//
|
|
4
|
-
// connects.
|
|
1
|
+
// What a file declares with `presence()`: what the bot shows under its
|
|
2
|
+
// name. It is checked when it loads and turned into what the gateway
|
|
3
|
+
// sends, so a wrong status or type is explained before the bot connects.
|
|
5
4
|
import { ActivityType, } from '../discord/types/gateway-events.js';
|
|
6
|
-
import {
|
|
5
|
+
import { isPresenceDeclaration } from './presence.js';
|
|
7
6
|
const fail = message => {
|
|
8
7
|
throw new TypeError(message);
|
|
9
8
|
};
|
|
@@ -36,8 +35,8 @@ const STREAM_URL = /^https:\/\/(?:www\.)?(?:twitch\.tv|youtube\.com)\//;
|
|
|
36
35
|
const list = (words) => words.map(word => `'${word}'`).join(', ');
|
|
37
36
|
const EXAMPLE = `presence({ status: 'online', activity: { type: 'watching', name: 'over the server' } })`;
|
|
38
37
|
/**
|
|
39
|
-
* What a bot without a presence
|
|
40
|
-
*
|
|
38
|
+
* What a bot without a presence shows: online, doing nothing. Sent when
|
|
39
|
+
* the declaration is removed while the bot runs.
|
|
41
40
|
*/
|
|
42
41
|
export const DEFAULT_PRESENCE = Object.freeze({
|
|
43
42
|
since: null,
|
|
@@ -65,30 +64,16 @@ function toRaw(config) {
|
|
|
65
64
|
};
|
|
66
65
|
}
|
|
67
66
|
/**
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
* ```ts
|
|
72
|
-
* // src/presence.ts
|
|
73
|
-
* import { presence } from 'chapterjs';
|
|
74
|
-
*
|
|
75
|
-
* export default presence({ activity: { type: 'playing', name: '/help' } });
|
|
76
|
-
* ```
|
|
67
|
+
* The presence of the bot, declared once anywhere in `src/` with
|
|
68
|
+
* `presence()` and exported.
|
|
77
69
|
*/
|
|
78
|
-
export const
|
|
79
|
-
folder: 'presence',
|
|
80
|
-
single: true,
|
|
70
|
+
export const presenceDeclaration = {
|
|
81
71
|
one: 'presence',
|
|
82
72
|
many: 'presences',
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
return fail('default' in exports
|
|
88
|
-
? `The default export of this file must be what presence() returns: ${example}`
|
|
89
|
-
: `This file has no default export. It should look like: ${example}`);
|
|
90
|
-
}
|
|
91
|
-
const config = file.config;
|
|
73
|
+
is: isPresenceDeclaration,
|
|
74
|
+
list: false,
|
|
75
|
+
read(value) {
|
|
76
|
+
const config = value.config;
|
|
92
77
|
if (!isRecord(config)) {
|
|
93
78
|
return fail(`presence() needs an object: ${EXAMPLE}`);
|
|
94
79
|
}
|
|
@@ -26,22 +26,22 @@ export interface StreamingActivity {
|
|
|
26
26
|
}
|
|
27
27
|
/** What the bot is doing. */
|
|
28
28
|
export type Activity = PlainActivity | StreamingActivity;
|
|
29
|
-
/** What
|
|
29
|
+
/** What a file gives to `presence()`. */
|
|
30
30
|
export interface PresenceConfig {
|
|
31
31
|
/** The status of the bot. `'online'` by default. */
|
|
32
32
|
status?: PresenceStatusName;
|
|
33
33
|
/** What the bot is doing. Nothing by default. */
|
|
34
34
|
activity?: Activity;
|
|
35
35
|
}
|
|
36
|
-
/** What `presence()` returns: the
|
|
37
|
-
export interface
|
|
36
|
+
/** What `presence()` returns: a declaration the framework finds in the exports of a file. */
|
|
37
|
+
export interface PresenceDeclaration {
|
|
38
38
|
/** What the file gave to `presence()`, not checked yet. */
|
|
39
39
|
readonly config: unknown;
|
|
40
40
|
}
|
|
41
41
|
/**
|
|
42
42
|
* Declares the presence of the bot: its status and what it is doing, shown
|
|
43
|
-
* under its name. Export the result
|
|
44
|
-
*
|
|
43
|
+
* under its name. Export the result from any file of `src/`, once; the bot
|
|
44
|
+
* has it as soon as it is connected.
|
|
45
45
|
*
|
|
46
46
|
* ```ts
|
|
47
47
|
* import { presence } from 'chapterjs';
|
|
@@ -52,6 +52,6 @@ export interface PresenceFile {
|
|
|
52
52
|
* });
|
|
53
53
|
* ```
|
|
54
54
|
*/
|
|
55
|
-
export declare function presence(config: PresenceConfig):
|
|
55
|
+
export declare function presence(config: PresenceConfig): PresenceDeclaration;
|
|
56
56
|
/** Whether a value was made by `presence()`. */
|
|
57
|
-
export declare function
|
|
57
|
+
export declare function isPresenceDeclaration(value: unknown): value is PresenceDeclaration;
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
// `presence()`, as
|
|
2
|
-
//
|
|
1
|
+
// `presence()`, as user files import it from 'chapterjs': what the bot
|
|
2
|
+
// shows under its name, its status and what it is doing.
|
|
3
3
|
// https://docs.discord.com/developers/events/gateway-events#update-presence
|
|
4
4
|
const BRAND = Symbol.for('chapterjs.presence');
|
|
5
5
|
/**
|
|
6
6
|
* Declares the presence of the bot: its status and what it is doing, shown
|
|
7
|
-
* under its name. Export the result
|
|
8
|
-
*
|
|
7
|
+
* under its name. Export the result from any file of `src/`, once; the bot
|
|
8
|
+
* has it as soon as it is connected.
|
|
9
9
|
*
|
|
10
10
|
* ```ts
|
|
11
11
|
* import { presence } from 'chapterjs';
|
|
@@ -20,7 +20,7 @@ export function presence(config) {
|
|
|
20
20
|
return Object.freeze({ [BRAND]: true, config });
|
|
21
21
|
}
|
|
22
22
|
/** Whether a value was made by `presence()`. */
|
|
23
|
-
export function
|
|
23
|
+
export function isPresenceDeclaration(value) {
|
|
24
24
|
return (typeof value === 'object' &&
|
|
25
25
|
value !== null &&
|
|
26
26
|
value[BRAND] === true);
|
|
@@ -18,7 +18,7 @@ export interface Context {
|
|
|
18
18
|
userId: Snowflake;
|
|
19
19
|
applicationId: Snowflake;
|
|
20
20
|
} | null;
|
|
21
|
-
/** The texts of
|
|
21
|
+
/** The texts of the language files; `null` without a language. */
|
|
22
22
|
messages: LoadedMessages | null;
|
|
23
23
|
/** Where the bot is in voice; `null` outside a running bot. */
|
|
24
24
|
voice: VoiceManager | null;
|
|
@@ -94,7 +94,7 @@ export declare class CommandInteraction extends Interaction {
|
|
|
94
94
|
});
|
|
95
95
|
/**
|
|
96
96
|
* Opens a form, as the answer: `showModal(report({ userId }))`, with a
|
|
97
|
-
* form
|
|
97
|
+
* form declared with `modal()`. It must be the first answer, and the
|
|
98
98
|
* person has as long as they want to fill it in.
|
|
99
99
|
* @see https://docs.discord.com/developers/interactions/receiving-and-responding#interaction-response-object-modal
|
|
100
100
|
*/
|
|
@@ -128,7 +128,7 @@ export declare class ComponentInteraction extends Interaction {
|
|
|
128
128
|
deferUpdate(): Promise<void>;
|
|
129
129
|
/**
|
|
130
130
|
* Opens a form, as the answer: `showModal(report({ userId }))`, with a
|
|
131
|
-
* form
|
|
131
|
+
* form declared with `modal()`. It must be the first answer.
|
|
132
132
|
* @see https://docs.discord.com/developers/interactions/receiving-and-responding#interaction-response-object-modal
|
|
133
133
|
*/
|
|
134
134
|
showModal(form: ModalComponent): Promise<void>;
|
|
@@ -75,7 +75,7 @@ export class Interaction extends IdStructure {
|
|
|
75
75
|
openModal = (interaction, form) => {
|
|
76
76
|
const rendered = renderedOf(form, 'The form given to showModal()');
|
|
77
77
|
if (rendered.kind !== 'modal') {
|
|
78
|
-
throw new TypeError(`showModal() opens a form
|
|
78
|
+
throw new TypeError(`showModal() opens a form declared with modal(), got a ${rendered.kind}.`);
|
|
79
79
|
}
|
|
80
80
|
return interaction.#run(async () => {
|
|
81
81
|
if (interaction.#state !== 'waiting') {
|
|
@@ -313,7 +313,7 @@ export class CommandInteraction extends Interaction {
|
|
|
313
313
|
}
|
|
314
314
|
/**
|
|
315
315
|
* Opens a form, as the answer: `showModal(report({ userId }))`, with a
|
|
316
|
-
* form
|
|
316
|
+
* form declared with `modal()`. It must be the first answer, and the
|
|
317
317
|
* person has as long as they want to fill it in.
|
|
318
318
|
* @see https://docs.discord.com/developers/interactions/receiving-and-responding#interaction-response-object-modal
|
|
319
319
|
*/
|
|
@@ -353,7 +353,7 @@ export class ComponentInteraction extends Interaction {
|
|
|
353
353
|
}
|
|
354
354
|
/**
|
|
355
355
|
* Opens a form, as the answer: `showModal(report({ userId }))`, with a
|
|
356
|
-
* form
|
|
356
|
+
* form declared with `modal()`. It must be the first answer.
|
|
357
357
|
* @see https://docs.discord.com/developers/interactions/receiving-and-responding#interaction-response-object-modal
|
|
358
358
|
*/
|
|
359
359
|
showModal(form) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type AssetFile } from '../assets/asset.js';
|
|
2
|
-
import { type
|
|
2
|
+
import { type EmbedDeclaration } from '../components/embed.js';
|
|
3
3
|
import type { MessageComponent } from '../components/instance.js';
|
|
4
4
|
import type { Snowflake } from '../discord/types/common.js';
|
|
5
5
|
import { type CreateMessageJSONParams, type RawAllowedMentions, type RawEmbed } from '../discord/types/message.js';
|
|
@@ -39,11 +39,11 @@ export interface FileInput {
|
|
|
39
39
|
export interface MessageOptions {
|
|
40
40
|
/** The text of the message (up to 2000 characters). */
|
|
41
41
|
content?: string;
|
|
42
|
-
/** Up to 10 embeds: written here, or
|
|
43
|
-
embeds?: (Embed |
|
|
42
|
+
/** Up to 10 embeds: written here, or declared with `embed()`. */
|
|
43
|
+
embeds?: (Embed | EmbedDeclaration<[]>)[];
|
|
44
44
|
/**
|
|
45
45
|
* Buttons, select menus and layout components, made by ChapterJS: the
|
|
46
|
-
*
|
|
46
|
+
* components declared with `button()`, `select()`... and `row()`, `text()`, `container()`...
|
|
47
47
|
*/
|
|
48
48
|
components?: MessageComponent[];
|
|
49
49
|
/** Up to 10 files to attach: their content, or `asset()` for a file of `public/`. */
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// expects. Used by everything that sends a message: channels, replies,
|
|
3
3
|
// webhooks, and later interactions.
|
|
4
4
|
import { isAssetFile, readAsset } from '../assets/asset.js';
|
|
5
|
-
import {
|
|
5
|
+
import { isEmbedDeclaration, } from '../components/embed.js';
|
|
6
6
|
import { renderComponents } from '../components/render.js';
|
|
7
7
|
import { Limits } from '../discord/api.js';
|
|
8
8
|
import { MessageFlags, } from '../discord/types/message.js';
|
|
@@ -41,16 +41,16 @@ export function checkEmbed(embed, name) {
|
|
|
41
41
|
});
|
|
42
42
|
return total;
|
|
43
43
|
}
|
|
44
|
-
/** The embeds of a message, with the
|
|
44
|
+
/** The embeds of a message, with the ones declared with `embed()` called. */
|
|
45
45
|
function resolveEmbeds(embeds) {
|
|
46
46
|
if (!Array.isArray(embeds)) {
|
|
47
47
|
throw new TypeError(`The embeds of a message are a list, got ${typeof embeds}.`);
|
|
48
48
|
}
|
|
49
49
|
return embeds.map((embed, index) => {
|
|
50
|
-
if (
|
|
50
|
+
if (isEmbedDeclaration(embed))
|
|
51
51
|
return embed();
|
|
52
52
|
if (typeof embed === 'function') {
|
|
53
|
-
throw new TypeError(`Embed ${index + 1} of the message is a function: an embed is an object like { title: '...' }, or
|
|
53
|
+
throw new TypeError(`Embed ${index + 1} of the message is a function: an embed is an object like { title: '...' }, or one declared with embed().`);
|
|
54
54
|
}
|
|
55
55
|
return embed;
|
|
56
56
|
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Declaration } from '../loader/loader.js';
|
|
2
|
+
import { type Schedule } from './schedule.js';
|
|
3
|
+
import { type TaskContext } from './task.js';
|
|
4
|
+
/** A task, checked: what the scheduler works with. */
|
|
5
|
+
export interface LoadedTask {
|
|
6
|
+
/** The name of the task: the name of the export that declares it. */
|
|
7
|
+
readonly name: string;
|
|
8
|
+
readonly schedule: Schedule;
|
|
9
|
+
readonly onStart: boolean;
|
|
10
|
+
readonly run: (context: TaskContext) => unknown;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* A task, declared anywhere in `src/` with `task()` and exported: the name
|
|
14
|
+
* of the export is the name of the task.
|
|
15
|
+
*/
|
|
16
|
+
export declare const taskDeclaration: Declaration<LoadedTask>;
|
|
@@ -1,49 +1,27 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
//
|
|
1
|
+
// What a file declares with `task()`, checked when it loads, so a wrong
|
|
2
|
+
// duration or cron is explained before the bot waits for it. The name of
|
|
3
|
+
// the export is the name of the task.
|
|
4
4
|
import { parseCron, parseEvery } from './schedule.js';
|
|
5
|
-
import {
|
|
5
|
+
import { isTaskDeclaration, } from './task.js';
|
|
6
6
|
const fail = (message) => {
|
|
7
7
|
throw new TypeError(message);
|
|
8
8
|
};
|
|
9
9
|
const isRecord = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
10
10
|
const KEYS = ['every', 'cron', 'timezone', 'onStart', 'run'];
|
|
11
|
+
const EXAMPLE = `task({ every: '10m', run() { ... } })`;
|
|
11
12
|
/**
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* ```ts
|
|
16
|
-
* // src/tasks/report.ts
|
|
17
|
-
* import { task } from 'chapterjs';
|
|
18
|
-
*
|
|
19
|
-
* export default task({
|
|
20
|
-
* cron: '0 9 * * 1',
|
|
21
|
-
* async run({ guilds }) { ... },
|
|
22
|
-
* });
|
|
23
|
-
* ```
|
|
13
|
+
* A task, declared anywhere in `src/` with `task()` and exported: the name
|
|
14
|
+
* of the export is the name of the task.
|
|
24
15
|
*/
|
|
25
|
-
export const
|
|
26
|
-
folder: 'tasks',
|
|
16
|
+
export const taskDeclaration = {
|
|
27
17
|
one: 'task',
|
|
28
18
|
many: 'tasks',
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
},
|
|
36
|
-
read(exports, path) {
|
|
37
|
-
const file = exports.default;
|
|
38
|
-
const example = `import { task } from 'chapterjs'; export default task({ every: '10m', run() { ... } })`;
|
|
39
|
-
if (!isTaskFile(file)) {
|
|
40
|
-
return fail('default' in exports
|
|
41
|
-
? `The default export of this file must be what task() returns: ${example}`
|
|
42
|
-
: `This file has no default export. It should look like: ${example}`);
|
|
43
|
-
}
|
|
44
|
-
const config = file.config;
|
|
19
|
+
is: isTaskDeclaration,
|
|
20
|
+
list: false,
|
|
21
|
+
read(value, { name }) {
|
|
22
|
+
const config = value.config;
|
|
45
23
|
if (!isRecord(config)) {
|
|
46
|
-
return fail(`task() needs an object:
|
|
24
|
+
return fail(`task() needs an object: ${EXAMPLE}`);
|
|
47
25
|
}
|
|
48
26
|
for (const key of Object.keys(config)) {
|
|
49
27
|
if (!KEYS.includes(key)) {
|
|
@@ -69,7 +47,7 @@ export const tasksConvention = {
|
|
|
69
47
|
fail(`This task has no "run": the function to run each time, like async run({ guilds }) { ... }`);
|
|
70
48
|
}
|
|
71
49
|
return Object.freeze({
|
|
72
|
-
name
|
|
50
|
+
name,
|
|
73
51
|
schedule,
|
|
74
52
|
onStart: config.onStart === true,
|
|
75
53
|
run: config.run,
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import type { TaskContext } from './task.js';
|
|
2
|
-
import type { LoadedTask } from './
|
|
2
|
+
import type { LoadedTask } from './declaration.js';
|
|
3
3
|
/** A task, with the file it comes from (to report its errors). */
|
|
4
4
|
export interface TaskEntry {
|
|
5
5
|
file: string;
|
|
6
|
+
/** The export it is: its name is the name of the task. */
|
|
7
|
+
export: string;
|
|
6
8
|
task: LoadedTask;
|
|
7
9
|
}
|
|
8
10
|
/** What runs the tasks. */
|
package/dist/tasks/scheduler.js
CHANGED
|
@@ -17,12 +17,12 @@ export class TimerScheduler {
|
|
|
17
17
|
const started = [];
|
|
18
18
|
for (const entry of entries) {
|
|
19
19
|
const key = scheduleKey(entry.task.schedule);
|
|
20
|
-
const previous = this.#tasks.get(entry.
|
|
20
|
+
const previous = this.#tasks.get(entry.task.name);
|
|
21
21
|
if (previous && previous.key === key) {
|
|
22
22
|
// Same timing: the next time stays, only the code changes.
|
|
23
23
|
previous.entry = entry;
|
|
24
|
-
kept.set(entry.
|
|
25
|
-
this.#tasks.delete(entry.
|
|
24
|
+
kept.set(entry.task.name, previous);
|
|
25
|
+
this.#tasks.delete(entry.task.name);
|
|
26
26
|
continue;
|
|
27
27
|
}
|
|
28
28
|
const scheduled = {
|
|
@@ -32,7 +32,7 @@ export class TimerScheduler {
|
|
|
32
32
|
timer: null,
|
|
33
33
|
running: false,
|
|
34
34
|
};
|
|
35
|
-
kept.set(entry.
|
|
35
|
+
kept.set(entry.task.name, scheduled);
|
|
36
36
|
started.push(scheduled);
|
|
37
37
|
}
|
|
38
38
|
// What is left was removed, or changed timing: it starts over.
|
package/dist/tasks/task.d.ts
CHANGED
|
@@ -47,22 +47,21 @@ export interface CronTaskConfig extends TaskConfigBase {
|
|
|
47
47
|
timezone?: string;
|
|
48
48
|
every?: never;
|
|
49
49
|
}
|
|
50
|
-
/** What a
|
|
50
|
+
/** What a file gives to `task()`: when it runs, and what it does. */
|
|
51
51
|
export type TaskConfig = EveryTaskConfig | CronTaskConfig;
|
|
52
|
-
/** What `task()` returns: the
|
|
53
|
-
export interface
|
|
52
|
+
/** What `task()` returns: a declaration the framework finds in the exports of a file. */
|
|
53
|
+
export interface TaskDeclaration {
|
|
54
54
|
/** What the file gave to `task()`, not checked yet. */
|
|
55
55
|
readonly config: unknown;
|
|
56
56
|
}
|
|
57
57
|
/**
|
|
58
|
-
* Declares a task: something the bot does by itself. Export the result
|
|
59
|
-
*
|
|
60
|
-
* the name of the task.
|
|
58
|
+
* Declares a task: something the bot does by itself. Export the result
|
|
59
|
+
* from any file of `src/`: the name of the export is the name of the task.
|
|
61
60
|
*
|
|
62
61
|
* ```ts
|
|
63
62
|
* import { task } from 'chapterjs';
|
|
64
63
|
*
|
|
65
|
-
* export
|
|
64
|
+
* export const watch = task({
|
|
66
65
|
* every: '10m',
|
|
67
66
|
* async run({ guilds }) {
|
|
68
67
|
* console.log(`Watching ${guilds.size} servers`);
|
|
@@ -70,7 +69,7 @@ export interface TaskFile {
|
|
|
70
69
|
* });
|
|
71
70
|
* ```
|
|
72
71
|
*/
|
|
73
|
-
export declare function task(config: TaskConfig):
|
|
72
|
+
export declare function task(config: TaskConfig): TaskDeclaration;
|
|
74
73
|
/** Whether a value was made by `task()`. */
|
|
75
|
-
export declare function
|
|
74
|
+
export declare function isTaskDeclaration(value: unknown): value is TaskDeclaration;
|
|
76
75
|
export {};
|
package/dist/tasks/task.js
CHANGED
|
@@ -1,15 +1,14 @@
|
|
|
1
|
-
// `task()`, as
|
|
2
|
-
//
|
|
1
|
+
// `task()`, as user files import it from 'chapterjs': what the bot does by
|
|
2
|
+
// itself, at intervals or at given times.
|
|
3
3
|
const BRAND = Symbol.for('chapterjs.task');
|
|
4
4
|
/**
|
|
5
|
-
* Declares a task: something the bot does by itself. Export the result
|
|
6
|
-
*
|
|
7
|
-
* the name of the task.
|
|
5
|
+
* Declares a task: something the bot does by itself. Export the result
|
|
6
|
+
* from any file of `src/`: the name of the export is the name of the task.
|
|
8
7
|
*
|
|
9
8
|
* ```ts
|
|
10
9
|
* import { task } from 'chapterjs';
|
|
11
10
|
*
|
|
12
|
-
* export
|
|
11
|
+
* export const watch = task({
|
|
13
12
|
* every: '10m',
|
|
14
13
|
* async run({ guilds }) {
|
|
15
14
|
* console.log(`Watching ${guilds.size} servers`);
|
|
@@ -21,7 +20,7 @@ export function task(config) {
|
|
|
21
20
|
return Object.freeze({ [BRAND]: true, config });
|
|
22
21
|
}
|
|
23
22
|
/** Whether a value was made by `task()`. */
|
|
24
|
-
export function
|
|
23
|
+
export function isTaskDeclaration(value) {
|
|
25
24
|
return (typeof value === 'object' &&
|
|
26
25
|
value !== null &&
|
|
27
26
|
value[BRAND] === true);
|
package/package.json
CHANGED
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import type { Convention } from '../loader/loader.js';
|
|
2
|
-
import { type LoadedButton } from './button.js';
|
|
3
|
-
import { isEmbedFile } from './embed.js';
|
|
4
|
-
import { type LoadedModal } from './modal.js';
|
|
5
|
-
import { type LoadedSelect } from './select.js';
|
|
6
|
-
/** An embed file, checked (when it is static). */
|
|
7
|
-
export interface LoadedEmbed {
|
|
8
|
-
readonly kind: 'embed';
|
|
9
|
-
readonly path: string;
|
|
10
|
-
}
|
|
11
|
-
/** A component file, checked: what the router works with. */
|
|
12
|
-
export type LoadedComponent = LoadedButton | LoadedSelect | LoadedModal | LoadedEmbed;
|
|
13
|
-
/**
|
|
14
|
-
* `src/components/`: one folder per kind of component (`buttons/`,
|
|
15
|
-
* `selects/`, `modals/`, `embeds/`), and in it one file per component,
|
|
16
|
-
* whose path is its id. The file exports by default what `button()`,
|
|
17
|
-
* `select()`, `modal()` or `embed()` returns.
|
|
18
|
-
*/
|
|
19
|
-
export declare const componentsConvention: Convention<LoadedComponent>;
|
|
20
|
-
/** The files that are the same component (through `(group)` folders): the later one is left out. */
|
|
21
|
-
export declare function findDuplicates<T extends {
|
|
22
|
-
file: string;
|
|
23
|
-
component: {
|
|
24
|
-
path: string;
|
|
25
|
-
};
|
|
26
|
-
}>(entries: readonly T[]): {
|
|
27
|
-
valid: T[];
|
|
28
|
-
conflicts: {
|
|
29
|
-
file: string;
|
|
30
|
-
message: string;
|
|
31
|
-
}[];
|
|
32
|
-
};
|
|
33
|
-
/** Whether a file holds an embed: nothing to route. */
|
|
34
|
-
export declare const isEmbedLoaded: (component: LoadedComponent) => component is LoadedEmbed;
|
|
35
|
-
export { isEmbedFile };
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import { type PieceKind, type Rendered } from './instance.js';
|
|
2
|
-
/** The kinds of files of `src/components/`, which are its folders. */
|
|
3
|
-
export type ComponentKind = 'button' | 'select' | 'modal' | 'embed';
|
|
4
|
-
export declare const COMPONENT_FOLDERS: Record<string, ComponentKind>;
|
|
5
|
-
/** A file as the loader knows it: what it declared, and once loaded, how to make instances. */
|
|
6
|
-
export interface ComponentFileState {
|
|
7
|
-
readonly kind: ComponentKind;
|
|
8
|
-
readonly config: unknown;
|
|
9
|
-
/** The name of the file for messages, before it is bound: `button()`. */
|
|
10
|
-
readonly made: string;
|
|
11
|
-
bound?: {
|
|
12
|
-
/** The path of the file inside `src/components/`, without extension. */
|
|
13
|
-
path: string;
|
|
14
|
-
/** Makes an instance from what the file was called with. */
|
|
15
|
-
render: (args: readonly unknown[]) => Rendered | object;
|
|
16
|
-
};
|
|
17
|
-
}
|
|
18
|
-
/** The state of a component file, if the value is one. */
|
|
19
|
-
export declare function fileStateOf(value: unknown): ComponentFileState | undefined;
|
|
20
|
-
/**
|
|
21
|
-
* Makes a component file. `asPiece` says whether the file itself stands
|
|
22
|
-
* for its instance when it carries no data (a button, a menu, a modal: not
|
|
23
|
-
* an embed, which is only a function).
|
|
24
|
-
*/
|
|
25
|
-
export declare function createFile(kind: ComponentKind, config: unknown, { asPiece, pieceKind }: {
|
|
26
|
-
asPiece: boolean;
|
|
27
|
-
pieceKind?: PieceKind;
|
|
28
|
-
}): (...args: unknown[]) => unknown;
|
|
29
|
-
/** Binds a file to its path and to what makes its instances. */
|
|
30
|
-
export declare function bindFile(file: unknown, bound: NonNullable<ComponentFileState['bound']>): void;
|