@discordkit/core 3.1.0 → 4.0.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/CHANGELOG.md +416 -0
- package/README.md +52 -0
- package/dist/index.d.mts +23 -0
- package/dist/index.mjs +23 -0
- package/dist/requests/DiscordSession.d.mts +30 -0
- package/dist/requests/DiscordSession.mjs +202 -0
- package/dist/requests/addParams.d.mts +15 -0
- package/dist/requests/addParams.mjs +24 -0
- package/dist/requests/buildURL.d.mts +7 -0
- package/dist/requests/buildURL.mjs +7 -0
- package/dist/requests/getAsset.d.mts +7 -0
- package/dist/requests/getAsset.mjs +6 -0
- package/dist/requests/index.d.mts +9 -0
- package/dist/requests/index.mjs +9 -0
- package/dist/requests/methods.d.mts +63 -0
- package/dist/requests/methods.mjs +10 -0
- package/dist/requests/request.d.mts +27 -0
- package/dist/requests/request.mjs +29 -0
- package/dist/requests/toProcedure.d.mts +40 -0
- package/dist/requests/toProcedure.mjs +27 -0
- package/dist/requests/toQuery.d.mts +36 -0
- package/dist/requests/toQuery.mjs +17 -0
- package/dist/requests/toValidated.d.mts +16 -0
- package/dist/requests/toValidated.mjs +25 -0
- package/dist/requests/verifyKey.d.mts +13 -0
- package/dist/requests/verifyKey.mjs +63 -0
- package/dist/utils/isBetween.d.mts +4 -0
- package/dist/utils/isBetween.mjs +4 -0
- package/dist/utils/{isNonNullable.d.ts → isNonNullable.d.mts} +5 -2
- package/dist/utils/isNonNullable.mjs +22 -0
- package/dist/utils/isNumericString.d.mts +4 -0
- package/dist/utils/isNumericString.mjs +4 -0
- package/dist/utils/isObject.d.mts +4 -0
- package/dist/utils/isObject.mjs +4 -0
- package/dist/utils/sleep.d.mts +7 -0
- package/dist/utils/sleep.mjs +7 -0
- package/dist/utils/toCamelCase.d.mts +4 -0
- package/dist/utils/toCamelCase.mjs +4 -0
- package/dist/utils/toCamelKeys.d.mts +6 -0
- package/dist/utils/toCamelKeys.mjs +13 -0
- package/dist/utils/toSnakeCase.d.mts +4 -0
- package/dist/utils/toSnakeCase.mjs +4 -0
- package/dist/utils/toSnakeKeys.d.mts +6 -0
- package/dist/utils/toSnakeKeys.mjs +13 -0
- package/dist/validations/asDigits.d.mts +12 -0
- package/dist/validations/asDigits.mjs +10 -0
- package/dist/validations/asInteger.d.mts +12 -0
- package/dist/validations/asInteger.mjs +10 -0
- package/dist/validations/bitfield.d.mts +23 -0
- package/dist/validations/bitfield.mjs +20 -0
- package/dist/validations/boundedArray.d.mts +13 -0
- package/dist/validations/boundedArray.mjs +9 -0
- package/dist/validations/boundedInteger.d.mts +13 -0
- package/dist/validations/boundedInteger.mjs +9 -0
- package/dist/validations/boundedString.d.mts +14 -0
- package/dist/validations/boundedString.mjs +10 -0
- package/dist/validations/datauri.d.mts +24 -0
- package/dist/validations/datauri.mjs +17 -0
- package/dist/validations/fileUpload.d.mts +129 -0
- package/dist/validations/fileUpload.mjs +114 -0
- package/dist/validations/hasMimeType.d.mts +16 -0
- package/dist/validations/hasMimeType.mjs +16 -0
- package/dist/validations/hasSize.d.mts +10 -0
- package/dist/validations/hasSize.mjs +12 -0
- package/dist/validations/index.d.mts +15 -0
- package/dist/validations/index.mjs +15 -0
- package/dist/validations/schema.d.mts +102 -0
- package/dist/validations/schema.mjs +109 -0
- package/dist/validations/{snowflake.d.ts → snowflake.d.mts} +9 -7
- package/dist/validations/snowflake.mjs +28 -0
- package/dist/validations/{timestamp.d.ts → timestamp.d.mts} +5 -1
- package/dist/validations/timestamp.mjs +6 -0
- package/dist/validations/toBlob.d.mts +9 -0
- package/dist/validations/toBlob.mjs +17 -0
- package/dist/validations/url.d.mts +6 -0
- package/dist/validations/url.mjs +5 -0
- package/package.json +13 -23
- package/dist/index.d.ts +0 -2
- package/dist/index.js +0 -3
- package/dist/index.js.map +0 -1
- package/dist/requests/DiscordSession.d.ts +0 -16
- package/dist/requests/DiscordSession.js +0 -42
- package/dist/requests/DiscordSession.js.map +0 -1
- package/dist/requests/addParams.d.ts +0 -2
- package/dist/requests/addParams.js +0 -11
- package/dist/requests/addParams.js.map +0 -1
- package/dist/requests/buildURL.d.ts +0 -2
- package/dist/requests/buildURL.js +0 -4
- package/dist/requests/buildURL.js.map +0 -1
- package/dist/requests/getAsset.d.ts +0 -2
- package/dist/requests/getAsset.js +0 -3
- package/dist/requests/getAsset.js.map +0 -1
- package/dist/requests/index.d.ts +0 -7
- package/dist/requests/index.js +0 -8
- package/dist/requests/index.js.map +0 -1
- package/dist/requests/methods.d.ts +0 -13
- package/dist/requests/methods.js +0 -8
- package/dist/requests/methods.js.map +0 -1
- package/dist/requests/request.d.ts +0 -2
- package/dist/requests/request.js +0 -35
- package/dist/requests/request.js.map +0 -1
- package/dist/requests/toProcedure.d.ts +0 -31
- package/dist/requests/toProcedure.js +0 -36
- package/dist/requests/toProcedure.js.map +0 -1
- package/dist/requests/toQuery.d.ts +0 -28
- package/dist/requests/toQuery.js +0 -9
- package/dist/requests/toQuery.js.map +0 -1
- package/dist/requests/toValidated.d.ts +0 -13
- package/dist/requests/toValidated.js +0 -29
- package/dist/requests/toValidated.js.map +0 -1
- package/dist/utils/isBetween.d.ts +0 -1
- package/dist/utils/isBetween.js +0 -2
- package/dist/utils/isBetween.js.map +0 -1
- package/dist/utils/isNonNullable.js +0 -20
- package/dist/utils/isNonNullable.js.map +0 -1
- package/dist/utils/isNumericString.d.ts +0 -1
- package/dist/utils/isNumericString.js +0 -2
- package/dist/utils/isNumericString.js.map +0 -1
- package/dist/utils/isObject.d.ts +0 -1
- package/dist/utils/isObject.js +0 -2
- package/dist/utils/isObject.js.map +0 -1
- package/dist/utils/toCamelCase.d.ts +0 -1
- package/dist/utils/toCamelCase.js +0 -2
- package/dist/utils/toCamelCase.js.map +0 -1
- package/dist/utils/toCamelKeys.d.ts +0 -2
- package/dist/utils/toCamelKeys.js +0 -16
- package/dist/utils/toCamelKeys.js.map +0 -1
- package/dist/utils/toSnakeCase.d.ts +0 -1
- package/dist/utils/toSnakeCase.js +0 -4
- package/dist/utils/toSnakeCase.js.map +0 -1
- package/dist/utils/toSnakeKeys.d.ts +0 -2
- package/dist/utils/toSnakeKeys.js +0 -16
- package/dist/utils/toSnakeKeys.js.map +0 -1
- package/dist/validations/asDigits.d.ts +0 -6
- package/dist/validations/asDigits.js +0 -6
- package/dist/validations/asDigits.js.map +0 -1
- package/dist/validations/asInteger.d.ts +0 -6
- package/dist/validations/asInteger.js +0 -6
- package/dist/validations/asInteger.js.map +0 -1
- package/dist/validations/bitfield.d.ts +0 -17
- package/dist/validations/bitfield.js +0 -37
- package/dist/validations/bitfield.js.map +0 -1
- package/dist/validations/boundedArray.d.ts +0 -6
- package/dist/validations/boundedArray.js +0 -8
- package/dist/validations/boundedArray.js.map +0 -1
- package/dist/validations/boundedInteger.d.ts +0 -6
- package/dist/validations/boundedInteger.js +0 -8
- package/dist/validations/boundedInteger.js.map +0 -1
- package/dist/validations/boundedString.d.ts +0 -6
- package/dist/validations/boundedString.js +0 -8
- package/dist/validations/boundedString.js.map +0 -1
- package/dist/validations/datauri.d.ts +0 -20
- package/dist/validations/datauri.js +0 -20
- package/dist/validations/datauri.js.map +0 -1
- package/dist/validations/hasMimeType.d.ts +0 -10
- package/dist/validations/hasMimeType.js +0 -18
- package/dist/validations/hasMimeType.js.map +0 -1
- package/dist/validations/hasSize.d.ts +0 -5
- package/dist/validations/hasSize.js +0 -13
- package/dist/validations/hasSize.js.map +0 -1
- package/dist/validations/index.d.ts +0 -12
- package/dist/validations/index.js +0 -13
- package/dist/validations/index.js.map +0 -1
- package/dist/validations/snowflake.js +0 -39
- package/dist/validations/snowflake.js.map +0 -1
- package/dist/validations/timestamp.js +0 -4
- package/dist/validations/timestamp.js.map +0 -1
- package/dist/validations/toBlob.d.ts +0 -4
- package/dist/validations/toBlob.js +0 -19
- package/dist/validations/toBlob.js.map +0 -1
- package/dist/validations/url.d.ts +0 -2
- package/dist/validations/url.js +0 -3
- package/dist/validations/url.js.map +0 -1
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { isObject } from "../utils/isObject.mjs";
|
|
2
|
+
import * as v from "valibot";
|
|
3
|
+
//#region src/validations/fileUpload.ts
|
|
4
|
+
/** Type guard — true if `val` looks like a {@link FileUpload}. */
|
|
5
|
+
const isFileUpload = (val) => {
|
|
6
|
+
if (!isObject(val)) return false;
|
|
7
|
+
const record = val;
|
|
8
|
+
return typeof record.filename === `string` && typeof record.content?.arrayBuffer === `function`;
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Valibot schema for a single file upload. Validates the shape and
|
|
12
|
+
* surfaces a clean TypeScript type to consumers.
|
|
13
|
+
*/
|
|
14
|
+
const fileUpload = v.pipe(v.custom(isFileUpload, `Expected a FileUpload object`), v.title(`fileUpload`));
|
|
15
|
+
/**
|
|
16
|
+
* Walks a value and returns all {@link FileUpload}s within it, along
|
|
17
|
+
* with the path each one occupies.
|
|
18
|
+
*/
|
|
19
|
+
const collectFileUploads = (val, path = []) => {
|
|
20
|
+
if (isFileUpload(val)) return [{
|
|
21
|
+
path,
|
|
22
|
+
file: val
|
|
23
|
+
}];
|
|
24
|
+
if (Array.isArray(val)) return val.flatMap((item, i) => collectFileUploads(item, [...path, i]));
|
|
25
|
+
if (isObject(val)) return Object.entries(val).flatMap(([key, item]) => collectFileUploads(item, [...path, key]));
|
|
26
|
+
return [];
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* Returns a deep copy of `body` with the value at `path` replaced by
|
|
30
|
+
* `replacement`. Pure: never mutates `body` or any of its children.
|
|
31
|
+
*/
|
|
32
|
+
const replaceAtPath = (body, path, replacement) => {
|
|
33
|
+
if (path.length === 0) return replacement;
|
|
34
|
+
const [head, ...rest] = path;
|
|
35
|
+
if (Array.isArray(body)) return body.map((item, index) => index === head ? replaceAtPath(item, rest, replacement) : item);
|
|
36
|
+
if (isObject(body)) return Object.fromEntries(Object.entries(body).map(([key, item]) => [key, key === head ? replaceAtPath(item, rest, replacement) : item]));
|
|
37
|
+
return body;
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* Given a body that contains one or more {@link FileUpload}s, produce
|
|
41
|
+
* a `FormData` payload Discord can consume.
|
|
42
|
+
*
|
|
43
|
+
* Files are appended as `files[n]` parts; the rest of the body is
|
|
44
|
+
* snake_cased and JSON-stringified into a `payload_json` part.
|
|
45
|
+
*
|
|
46
|
+
* Each FileUpload in the body is replaced by its attachment placeholder
|
|
47
|
+
* `{ id: n }` so that an `attachments` array (if present) can reference
|
|
48
|
+
* uploads by index. Discord matches these placeholders to the
|
|
49
|
+
* corresponding `files[n]` parts.
|
|
50
|
+
*/
|
|
51
|
+
const toMultipartBody = (body, toSnakeKeys) => {
|
|
52
|
+
const uploads = collectFileUploads(body);
|
|
53
|
+
if (uploads.length === 0) throw new Error(`toMultipartBody called with a body containing no FileUploads`);
|
|
54
|
+
const payload = uploads.reduce((acc, { path }, index) => replaceAtPath(acc, path, { id: index }), body);
|
|
55
|
+
const fileEntries = uploads.map(({ file }, index) => {
|
|
56
|
+
const blob = file.contentType && file.content.type !== file.contentType ? new Blob([file.content], { type: file.contentType }) : file.content;
|
|
57
|
+
return [
|
|
58
|
+
`files[${index}]`,
|
|
59
|
+
blob,
|
|
60
|
+
file.filename
|
|
61
|
+
];
|
|
62
|
+
});
|
|
63
|
+
const payloadJson = new Blob([JSON.stringify(toSnakeKeys(payload))], { type: `application/json` });
|
|
64
|
+
const form = new FormData();
|
|
65
|
+
fileEntries.forEach(([name, blob, filename]) => form.append(name, blob, filename));
|
|
66
|
+
form.append(`payload_json`, payloadJson);
|
|
67
|
+
return form;
|
|
68
|
+
};
|
|
69
|
+
/**
|
|
70
|
+
* Sentinel symbol used to mark a validated body as multipart-eligible.
|
|
71
|
+
* Read by the request layer at serialization time.
|
|
72
|
+
*
|
|
73
|
+
* The marker is attached as a non-enumerable property on the parsed
|
|
74
|
+
* body so it doesn't leak into JSON serialization or the TypeScript
|
|
75
|
+
* output type.
|
|
76
|
+
*/
|
|
77
|
+
const MULTIPART_MARKER = Symbol.for(`@discordkit/core/multipart`);
|
|
78
|
+
/**
|
|
79
|
+
* Returns true if `body` should be serialized as `multipart/form-data`.
|
|
80
|
+
*
|
|
81
|
+
* Two signals trigger multipart serialization:
|
|
82
|
+
*
|
|
83
|
+
* 1. **Schema-tagged** — the body was parsed by a {@link multipart}-wrapped
|
|
84
|
+
* schema and at least one {@link FileUpload} was present. The wrapper
|
|
85
|
+
* stamps a non-enumerable {@link MULTIPART_MARKER} on the parsed value.
|
|
86
|
+
* This is the fast path for the validated flow (e.g., `toValidated`).
|
|
87
|
+
*
|
|
88
|
+
* 2. **Value-detected** — the body contains a {@link FileUpload} anywhere
|
|
89
|
+
* in its shape, regardless of whether it was validated. This safety net
|
|
90
|
+
* keeps consumers who bypass validation from silently dropping their
|
|
91
|
+
* files into a JSON body where `Blob` would serialize as `{}`.
|
|
92
|
+
*
|
|
93
|
+
* Either signal switches the request to multipart.
|
|
94
|
+
*/
|
|
95
|
+
const shouldSerializeAsMultipart = (body) => {
|
|
96
|
+
if (typeof body !== `object` || body === null) return false;
|
|
97
|
+
if (body[MULTIPART_MARKER] === true) return true;
|
|
98
|
+
return collectFileUploads(body).length > 0;
|
|
99
|
+
};
|
|
100
|
+
function multipart(entries, options) {
|
|
101
|
+
const base = v.object(entries);
|
|
102
|
+
const inner = options?.partial ? v.partial(base) : base;
|
|
103
|
+
return v.pipe(inner, v.transform((parsed) => {
|
|
104
|
+
if (!(collectFileUploads(parsed).length > 0)) return parsed;
|
|
105
|
+
return Object.defineProperty(parsed, MULTIPART_MARKER, {
|
|
106
|
+
value: true,
|
|
107
|
+
enumerable: false,
|
|
108
|
+
configurable: false,
|
|
109
|
+
writable: false
|
|
110
|
+
});
|
|
111
|
+
}));
|
|
112
|
+
}
|
|
113
|
+
//#endregion
|
|
114
|
+
export { MULTIPART_MARKER, collectFileUploads, fileUpload, isFileUpload, multipart, shouldSerializeAsMultipart, toMultipartBody };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { CheckAction } from "valibot";
|
|
2
|
+
|
|
3
|
+
//#region src/validations/hasMimeType.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Validation action to check if the given `datauri` string
|
|
6
|
+
* has one of the provided [MIME types](https://en.wikipedia.org/wiki/Media_type)
|
|
7
|
+
*
|
|
8
|
+
* @__NO_SIDE_EFFECTS__
|
|
9
|
+
*/
|
|
10
|
+
declare const hasMimeType: (/** an array of MIME types to validate against */
|
|
11
|
+
|
|
12
|
+
requirement: Array<`${string}/${string}`>, /** an optional error message on failed validation */
|
|
13
|
+
|
|
14
|
+
message?: "Received badly formatted Data URI") => CheckAction<string, typeof message>;
|
|
15
|
+
//#endregion
|
|
16
|
+
export { hasMimeType };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { extractDataURIMetadata } from "./datauri.mjs";
|
|
2
|
+
import { check } from "valibot";
|
|
3
|
+
//#region src/validations/hasMimeType.ts
|
|
4
|
+
/**
|
|
5
|
+
* Validation action to check if the given `datauri` string
|
|
6
|
+
* has one of the provided [MIME types](https://en.wikipedia.org/wiki/Media_type)
|
|
7
|
+
*
|
|
8
|
+
* @__NO_SIDE_EFFECTS__
|
|
9
|
+
*/
|
|
10
|
+
const hasMimeType = (requirement, message = `Received badly formatted Data URI`) => check((input) => {
|
|
11
|
+
const { mimeType } = extractDataURIMetadata(input);
|
|
12
|
+
if (typeof mimeType === `undefined`) return false;
|
|
13
|
+
return requirement.includes(mimeType);
|
|
14
|
+
}, message);
|
|
15
|
+
//#endregion
|
|
16
|
+
export { hasMimeType };
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { CheckAction } from "valibot";
|
|
2
|
+
|
|
3
|
+
//#region src/validations/hasSize.d.ts
|
|
4
|
+
/** @__NO_SIDE_EFFECTS__ */
|
|
5
|
+
declare const hasSize: (size: number | {
|
|
6
|
+
min?: number;
|
|
7
|
+
max?: number;
|
|
8
|
+
}, message?: "Data URI is the incorrect size") => CheckAction<string, typeof message>;
|
|
9
|
+
//#endregion
|
|
10
|
+
export { hasSize };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { extractDataURIMetadata, toBase64 } from "./datauri.mjs";
|
|
2
|
+
import { check } from "valibot";
|
|
3
|
+
//#region src/validations/hasSize.ts
|
|
4
|
+
/** @__NO_SIDE_EFFECTS__ */
|
|
5
|
+
const hasSize = (size, message = `Data URI is the incorrect size`) => check((dataURI) => {
|
|
6
|
+
const { data } = extractDataURIMetadata(dataURI);
|
|
7
|
+
if (typeof data === `undefined`) throw new Error(`Received badly formatted Data URI`);
|
|
8
|
+
const actual = toBase64(data).length;
|
|
9
|
+
return typeof size === `number` ? size === actual : actual >= (size.min ?? 0) && actual <= (size.max ?? Infinity);
|
|
10
|
+
}, message);
|
|
11
|
+
//#endregion
|
|
12
|
+
export { hasSize };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Flags, bitfield } from "./bitfield.mjs";
|
|
2
|
+
import { asDigits } from "./asDigits.mjs";
|
|
3
|
+
import { asInteger } from "./asInteger.mjs";
|
|
4
|
+
import { boundedArray } from "./boundedArray.mjs";
|
|
5
|
+
import { boundedInteger } from "./boundedInteger.mjs";
|
|
6
|
+
import { boundedString } from "./boundedString.mjs";
|
|
7
|
+
import { datauri, datauriRegex, extractDataURIMetadata, toBase64 } from "./datauri.mjs";
|
|
8
|
+
import { FileUpload, MULTIPART_MARKER, collectFileUploads, fileUpload, isFileUpload, multipart, shouldSerializeAsMultipart, toMultipartBody } from "./fileUpload.mjs";
|
|
9
|
+
import { hasMimeType } from "./hasMimeType.mjs";
|
|
10
|
+
import { omitFields, partialSchema, pickFields, requiredFields, schema, variantSchema } from "./schema.mjs";
|
|
11
|
+
import { toBlob } from "./toBlob.mjs";
|
|
12
|
+
import { DISCORD_EPOCH, snowflake, snowflakeToDate } from "./snowflake.mjs";
|
|
13
|
+
import { timestamp } from "./timestamp.mjs";
|
|
14
|
+
import { url } from "./url.mjs";
|
|
15
|
+
export { DISCORD_EPOCH, FileUpload, Flags, MULTIPART_MARKER, asDigits, asInteger, bitfield, boundedArray, boundedInteger, boundedString, collectFileUploads, datauri, datauriRegex, extractDataURIMetadata, fileUpload, hasMimeType, isFileUpload, multipart, omitFields, partialSchema, pickFields, requiredFields, schema, shouldSerializeAsMultipart, snowflake, snowflakeToDate, timestamp, toBase64, toBlob, toMultipartBody, url, variantSchema };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { MULTIPART_MARKER, collectFileUploads, fileUpload, isFileUpload, multipart, shouldSerializeAsMultipart, toMultipartBody } from "./fileUpload.mjs";
|
|
2
|
+
import { asDigits } from "./asDigits.mjs";
|
|
3
|
+
import { asInteger } from "./asInteger.mjs";
|
|
4
|
+
import { bitfield } from "./bitfield.mjs";
|
|
5
|
+
import { boundedArray } from "./boundedArray.mjs";
|
|
6
|
+
import { boundedInteger } from "./boundedInteger.mjs";
|
|
7
|
+
import { boundedString } from "./boundedString.mjs";
|
|
8
|
+
import { datauri, datauriRegex, extractDataURIMetadata, toBase64 } from "./datauri.mjs";
|
|
9
|
+
import { hasMimeType } from "./hasMimeType.mjs";
|
|
10
|
+
import { omitFields, partialSchema, pickFields, requiredFields, schema, variantSchema } from "./schema.mjs";
|
|
11
|
+
import { toBlob } from "./toBlob.mjs";
|
|
12
|
+
import { DISCORD_EPOCH, snowflake, snowflakeToDate } from "./snowflake.mjs";
|
|
13
|
+
import { timestamp } from "./timestamp.mjs";
|
|
14
|
+
import { url } from "./url.mjs";
|
|
15
|
+
export { DISCORD_EPOCH, MULTIPART_MARKER, asDigits, asInteger, bitfield, boundedArray, boundedInteger, boundedString, collectFileUploads, datauri, datauriRegex, extractDataURIMetadata, fileUpload, hasMimeType, isFileUpload, multipart, omitFields, partialSchema, pickFields, requiredFields, schema, shouldSerializeAsMultipart, snowflake, snowflakeToDate, timestamp, toBase64, toBlob, toMultipartBody, url, variantSchema };
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import * as v from "valibot";
|
|
2
|
+
|
|
3
|
+
//#region src/validations/schema.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Annotate a schema's *published* type as `v.GenericSchema<T>` so
|
|
6
|
+
* downstream `.d.ts` emit stops inlining the inner `ObjectSchema`'s
|
|
7
|
+
* entries map and instead references `T` by name. The runtime value
|
|
8
|
+
* is unchanged.
|
|
9
|
+
*
|
|
10
|
+
* Use at every type-defining schema export:
|
|
11
|
+
*
|
|
12
|
+
* ```ts
|
|
13
|
+
* const _userSchema = v.object({...});
|
|
14
|
+
* export interface User extends v.InferOutput<typeof _userSchema> {}
|
|
15
|
+
* export const userSchema = schema<User>(_userSchema);
|
|
16
|
+
* ```
|
|
17
|
+
*
|
|
18
|
+
* **Why this exists**
|
|
19
|
+
*
|
|
20
|
+
* Without annotation, `typeof userSchema` is the full
|
|
21
|
+
* `v.ObjectSchema<{ id: v.GenericSchema<string>, ... }>` shape. Every
|
|
22
|
+
* downstream file that references `userSchema` (Guild, Application,
|
|
23
|
+
* Member, …) re-inlines that entries map into its own emitted
|
|
24
|
+
* declarations. This causes:
|
|
25
|
+
*
|
|
26
|
+
* - dramatic duplication of nested object shapes in `.d.ts` output
|
|
27
|
+
* - slow IDE hover (tsserver expands the full shape on every
|
|
28
|
+
* reference)
|
|
29
|
+
* - occasional TS2502 / type-too-complex errors on deeply circular
|
|
30
|
+
* schemas (Channel, Message, …)
|
|
31
|
+
*
|
|
32
|
+
* After annotation, downstream `.d.ts` files reference `User` by
|
|
33
|
+
* name from `./user/types/User.d.mts` and never inline its body.
|
|
34
|
+
*
|
|
35
|
+
* **Trade-off**
|
|
36
|
+
*
|
|
37
|
+
* `v.partial(userSchema)`, `v.pick(userSchema, [...])`, `v.omit(...)`,
|
|
38
|
+
* and `userSchema.entries.id` stop type-checking because the constraint
|
|
39
|
+
* is `ObjectSchema<...>` not `GenericSchema<T>`. Use the
|
|
40
|
+
* {@link partialSchema}, {@link pickFields}, {@link omitFields},
|
|
41
|
+
* {@link requiredFields}, and {@link variantSchema} helpers below for
|
|
42
|
+
* the common cases.
|
|
43
|
+
*
|
|
44
|
+
* @__NO_SIDE_EFFECTS__
|
|
45
|
+
*/
|
|
46
|
+
declare const schema: <T>(s: v.GenericSchema<unknown>) => v.GenericSchema<T>;
|
|
47
|
+
/**
|
|
48
|
+
* Make every key of `T` optional, both at the type level and at
|
|
49
|
+
* runtime. Equivalent to Valibot's `v.partial`, but accepts an
|
|
50
|
+
* annotated `GenericSchema<T>` (which `v.partial` would reject because
|
|
51
|
+
* its constraint is `ObjectSchema<...>`).
|
|
52
|
+
*
|
|
53
|
+
* @__NO_SIDE_EFFECTS__
|
|
54
|
+
*/
|
|
55
|
+
declare const partialSchema: <T>(s: v.GenericSchema<T>) => v.GenericSchema<Partial<T>>;
|
|
56
|
+
/**
|
|
57
|
+
* Pick a subset of fields from `T` at both type and runtime levels.
|
|
58
|
+
* Equivalent to Valibot's `v.pick`. See {@link partialSchema}.
|
|
59
|
+
*
|
|
60
|
+
* @__NO_SIDE_EFFECTS__
|
|
61
|
+
*/
|
|
62
|
+
declare const pickFields: <T, K extends keyof T & string>(s: v.GenericSchema<T>, keys: readonly K[]) => v.GenericSchema<Pick<T, K>>;
|
|
63
|
+
/**
|
|
64
|
+
* Omit a subset of fields from `T` at both type and runtime levels.
|
|
65
|
+
* Equivalent to Valibot's `v.omit`. See {@link partialSchema}.
|
|
66
|
+
*
|
|
67
|
+
* @__NO_SIDE_EFFECTS__
|
|
68
|
+
*/
|
|
69
|
+
declare const omitFields: <T, K extends keyof T & string>(s: v.GenericSchema<T>, keys: readonly K[]) => v.GenericSchema<Omit<T, K>>;
|
|
70
|
+
/**
|
|
71
|
+
* Mark a subset of fields on `T` as required at both type and runtime
|
|
72
|
+
* levels. Equivalent to Valibot's `v.required`. See {@link partialSchema}.
|
|
73
|
+
*
|
|
74
|
+
* @__NO_SIDE_EFFECTS__
|
|
75
|
+
*/
|
|
76
|
+
declare const requiredFields: <T, K extends keyof T & string>(s: v.GenericSchema<T>, keys: readonly K[]) => v.GenericSchema<T & Required<Pick<T, K>>>;
|
|
77
|
+
/**
|
|
78
|
+
* Build a discriminated union schema. Equivalent to Valibot's
|
|
79
|
+
* `v.variant`, but accepts annotated `GenericSchema<T>` variants
|
|
80
|
+
* (which `v.variant` would reject because its constraint is
|
|
81
|
+
* `ObjectSchema<...>`).
|
|
82
|
+
*
|
|
83
|
+
* Prefer `v.variant` whenever variants share a literal discriminator
|
|
84
|
+
* field — it dispatches on that field at runtime, producing focused
|
|
85
|
+
* error messages and O(1) validation, vs. {@link v.union}'s O(n)
|
|
86
|
+
* try-each behavior.
|
|
87
|
+
*
|
|
88
|
+
* @example
|
|
89
|
+
* ```ts
|
|
90
|
+
* export const channelSchema = variantSchema<Channel>(`type`, [
|
|
91
|
+
* guildTextChannelSchema,
|
|
92
|
+
* guildVoiceChannelSchema,
|
|
93
|
+
* threadChannelSchema,
|
|
94
|
+
* // ...
|
|
95
|
+
* ]);
|
|
96
|
+
* ```
|
|
97
|
+
*
|
|
98
|
+
* @__NO_SIDE_EFFECTS__
|
|
99
|
+
*/
|
|
100
|
+
declare const variantSchema: <T>(key: keyof T & string, schemas: ReadonlyArray<v.GenericSchema<unknown>>) => v.GenericSchema<T>;
|
|
101
|
+
//#endregion
|
|
102
|
+
export { omitFields, partialSchema, pickFields, requiredFields, schema, variantSchema };
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import * as v from "valibot";
|
|
2
|
+
//#region src/validations/schema.ts
|
|
3
|
+
/**
|
|
4
|
+
* Annotate a schema's *published* type as `v.GenericSchema<T>` so
|
|
5
|
+
* downstream `.d.ts` emit stops inlining the inner `ObjectSchema`'s
|
|
6
|
+
* entries map and instead references `T` by name. The runtime value
|
|
7
|
+
* is unchanged.
|
|
8
|
+
*
|
|
9
|
+
* Use at every type-defining schema export:
|
|
10
|
+
*
|
|
11
|
+
* ```ts
|
|
12
|
+
* const _userSchema = v.object({...});
|
|
13
|
+
* export interface User extends v.InferOutput<typeof _userSchema> {}
|
|
14
|
+
* export const userSchema = schema<User>(_userSchema);
|
|
15
|
+
* ```
|
|
16
|
+
*
|
|
17
|
+
* **Why this exists**
|
|
18
|
+
*
|
|
19
|
+
* Without annotation, `typeof userSchema` is the full
|
|
20
|
+
* `v.ObjectSchema<{ id: v.GenericSchema<string>, ... }>` shape. Every
|
|
21
|
+
* downstream file that references `userSchema` (Guild, Application,
|
|
22
|
+
* Member, …) re-inlines that entries map into its own emitted
|
|
23
|
+
* declarations. This causes:
|
|
24
|
+
*
|
|
25
|
+
* - dramatic duplication of nested object shapes in `.d.ts` output
|
|
26
|
+
* - slow IDE hover (tsserver expands the full shape on every
|
|
27
|
+
* reference)
|
|
28
|
+
* - occasional TS2502 / type-too-complex errors on deeply circular
|
|
29
|
+
* schemas (Channel, Message, …)
|
|
30
|
+
*
|
|
31
|
+
* After annotation, downstream `.d.ts` files reference `User` by
|
|
32
|
+
* name from `./user/types/User.d.mts` and never inline its body.
|
|
33
|
+
*
|
|
34
|
+
* **Trade-off**
|
|
35
|
+
*
|
|
36
|
+
* `v.partial(userSchema)`, `v.pick(userSchema, [...])`, `v.omit(...)`,
|
|
37
|
+
* and `userSchema.entries.id` stop type-checking because the constraint
|
|
38
|
+
* is `ObjectSchema<...>` not `GenericSchema<T>`. Use the
|
|
39
|
+
* {@link partialSchema}, {@link pickFields}, {@link omitFields},
|
|
40
|
+
* {@link requiredFields}, and {@link variantSchema} helpers below for
|
|
41
|
+
* the common cases.
|
|
42
|
+
*
|
|
43
|
+
* @__NO_SIDE_EFFECTS__
|
|
44
|
+
*/
|
|
45
|
+
const schema = (s) => s;
|
|
46
|
+
/**
|
|
47
|
+
* Make every key of `T` optional, both at the type level and at
|
|
48
|
+
* runtime. Equivalent to Valibot's `v.partial`, but accepts an
|
|
49
|
+
* annotated `GenericSchema<T>` (which `v.partial` would reject because
|
|
50
|
+
* its constraint is `ObjectSchema<...>`).
|
|
51
|
+
*
|
|
52
|
+
* @__NO_SIDE_EFFECTS__
|
|
53
|
+
*/
|
|
54
|
+
const partialSchema = (s) => v.partial(s);
|
|
55
|
+
/**
|
|
56
|
+
* Pick a subset of fields from `T` at both type and runtime levels.
|
|
57
|
+
* Equivalent to Valibot's `v.pick`. See {@link partialSchema}.
|
|
58
|
+
*
|
|
59
|
+
* @__NO_SIDE_EFFECTS__
|
|
60
|
+
*/
|
|
61
|
+
const pickFields = (s, keys) => {
|
|
62
|
+
return v.pick(s, keys);
|
|
63
|
+
};
|
|
64
|
+
/**
|
|
65
|
+
* Omit a subset of fields from `T` at both type and runtime levels.
|
|
66
|
+
* Equivalent to Valibot's `v.omit`. See {@link partialSchema}.
|
|
67
|
+
*
|
|
68
|
+
* @__NO_SIDE_EFFECTS__
|
|
69
|
+
*/
|
|
70
|
+
const omitFields = (s, keys) => {
|
|
71
|
+
return v.omit(s, keys);
|
|
72
|
+
};
|
|
73
|
+
/**
|
|
74
|
+
* Mark a subset of fields on `T` as required at both type and runtime
|
|
75
|
+
* levels. Equivalent to Valibot's `v.required`. See {@link partialSchema}.
|
|
76
|
+
*
|
|
77
|
+
* @__NO_SIDE_EFFECTS__
|
|
78
|
+
*/
|
|
79
|
+
const requiredFields = (s, keys) => {
|
|
80
|
+
return v.required(s, keys);
|
|
81
|
+
};
|
|
82
|
+
/**
|
|
83
|
+
* Build a discriminated union schema. Equivalent to Valibot's
|
|
84
|
+
* `v.variant`, but accepts annotated `GenericSchema<T>` variants
|
|
85
|
+
* (which `v.variant` would reject because its constraint is
|
|
86
|
+
* `ObjectSchema<...>`).
|
|
87
|
+
*
|
|
88
|
+
* Prefer `v.variant` whenever variants share a literal discriminator
|
|
89
|
+
* field — it dispatches on that field at runtime, producing focused
|
|
90
|
+
* error messages and O(1) validation, vs. {@link v.union}'s O(n)
|
|
91
|
+
* try-each behavior.
|
|
92
|
+
*
|
|
93
|
+
* @example
|
|
94
|
+
* ```ts
|
|
95
|
+
* export const channelSchema = variantSchema<Channel>(`type`, [
|
|
96
|
+
* guildTextChannelSchema,
|
|
97
|
+
* guildVoiceChannelSchema,
|
|
98
|
+
* threadChannelSchema,
|
|
99
|
+
* // ...
|
|
100
|
+
* ]);
|
|
101
|
+
* ```
|
|
102
|
+
*
|
|
103
|
+
* @__NO_SIDE_EFFECTS__
|
|
104
|
+
*/
|
|
105
|
+
const variantSchema = (key, schemas) => {
|
|
106
|
+
return v.variant(key, schemas);
|
|
107
|
+
};
|
|
108
|
+
//#endregion
|
|
109
|
+
export { omitFields, partialSchema, pickFields, requiredFields, schema, variantSchema };
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
import * as v from "valibot";
|
|
2
|
+
|
|
3
|
+
//#region src/validations/snowflake.d.ts
|
|
2
4
|
/** UNIX timestamp representing the first second of 2015 */
|
|
3
|
-
|
|
5
|
+
declare const DISCORD_EPOCH = 1420070400000n;
|
|
4
6
|
/**
|
|
5
7
|
* Converts a `snowflake` string to a Date relative to the given epoch
|
|
6
8
|
*
|
|
7
9
|
* Uses Discord's epoch by default
|
|
8
10
|
*
|
|
9
11
|
* https://discord.com/developers/docs/reference#snowflakes
|
|
12
|
+
*
|
|
13
|
+
* @__NO_SIDE_EFFECTS__
|
|
10
14
|
*/
|
|
11
|
-
|
|
12
|
-
/** A snowflake string to convert */
|
|
13
|
-
val: string | number | bigint,
|
|
14
|
-
/** time in milliseconds to use as the epoch to derive a Date from */
|
|
15
|
-
epoch?: bigint) => Date;
|
|
15
|
+
declare const snowflakeToDate: (/** A snowflake string to convert */val: string | number | bigint, /** time in milliseconds to use as the epoch to derive a Date from */epoch?: bigint) => Date;
|
|
16
16
|
/**
|
|
17
17
|
* An up to 64-bit unsigned numeric value derived from a timestamp which
|
|
18
18
|
* serves as a unique identifier within Discord.
|
|
@@ -22,4 +22,6 @@ epoch?: bigint) => Date;
|
|
|
22
22
|
* by checking if it's derived timestamp is a valid time at or after
|
|
23
23
|
* the Discord epoch (the first second of 2015, ie: `1420070400000`).
|
|
24
24
|
*/
|
|
25
|
-
|
|
25
|
+
declare const snowflake: v.GenericSchema<string>;
|
|
26
|
+
//#endregion
|
|
27
|
+
export { DISCORD_EPOCH, snowflake, snowflakeToDate };
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { isNonNullable } from "../utils/isNonNullable.mjs";
|
|
2
|
+
import { isNumericString } from "../utils/isNumericString.mjs";
|
|
3
|
+
import * as v from "valibot";
|
|
4
|
+
//#region src/validations/snowflake.ts
|
|
5
|
+
/** UNIX timestamp representing the first second of 2015 */
|
|
6
|
+
const DISCORD_EPOCH = 1420070400000n;
|
|
7
|
+
/**
|
|
8
|
+
* Converts a `snowflake` string to a Date relative to the given epoch
|
|
9
|
+
*
|
|
10
|
+
* Uses Discord's epoch by default
|
|
11
|
+
*
|
|
12
|
+
* https://discord.com/developers/docs/reference#snowflakes
|
|
13
|
+
*
|
|
14
|
+
* @__NO_SIDE_EFFECTS__
|
|
15
|
+
*/
|
|
16
|
+
const snowflakeToDate = (val, epoch = DISCORD_EPOCH) => new Date(Number((BigInt(val) >> 22n) + epoch));
|
|
17
|
+
/**
|
|
18
|
+
* An up to 64-bit unsigned numeric value derived from a timestamp which
|
|
19
|
+
* serves as a unique identifier within Discord.
|
|
20
|
+
*
|
|
21
|
+
* Validates whether a given `number`, `bigint`, or numeric `string` is
|
|
22
|
+
* a valid [Snowflake](https://discord.com/developers/docs/reference#snowflakes)
|
|
23
|
+
* by checking if it's derived timestamp is a valid time at or after
|
|
24
|
+
* the Discord epoch (the first second of 2015, ie: `1420070400000`).
|
|
25
|
+
*/
|
|
26
|
+
const snowflake = v.pipe(v.custom((val) => isNonNullable(val) && (typeof val === `bigint` || typeof val === `number` || isNumericString(val)) && snowflakeToDate(val).getTime() >= 1420070400000n, `Invalid Snowflake`), v.title(`snowflake`));
|
|
27
|
+
//#endregion
|
|
28
|
+
export { DISCORD_EPOCH, snowflake, snowflakeToDate };
|
|
@@ -1,3 +1,7 @@
|
|
|
1
1
|
import * as v from "valibot";
|
|
2
|
+
|
|
3
|
+
//#region src/validations/timestamp.d.ts
|
|
2
4
|
/** An [ISO8601](https://www.loc.gov/standards/datetime/iso-tc154-wg5_n0038_iso_wd_8601-1_2016-02-16.pdf) timestamp */
|
|
3
|
-
|
|
5
|
+
declare const timestamp: v.GenericSchema<string>;
|
|
6
|
+
//#endregion
|
|
7
|
+
export { timestamp };
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import * as v from "valibot";
|
|
2
|
+
//#region src/validations/timestamp.ts
|
|
3
|
+
/** An [ISO8601](https://www.loc.gov/standards/datetime/iso-tc154-wg5_n0038_iso_wd_8601-1_2016-02-16.pdf) timestamp */
|
|
4
|
+
const timestamp = v.message(v.pipe(v.string(), v.isoTimestamp()), (issue) => `Expected a valid timestamp, received: ${issue.received}`);
|
|
5
|
+
//#endregion
|
|
6
|
+
export { timestamp };
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { extractDataURIMetadata, toBase64 } from "./datauri.mjs";
|
|
2
|
+
import { transform } from "valibot";
|
|
3
|
+
//#region src/validations/toBlob.ts
|
|
4
|
+
/**
|
|
5
|
+
* Transforms a `datauri` string to a Blob
|
|
6
|
+
*/
|
|
7
|
+
const toBlob = transform((dataURI) => {
|
|
8
|
+
const { mimeType, data } = extractDataURIMetadata(dataURI);
|
|
9
|
+
if (typeof mimeType === `undefined` || typeof data === `undefined`) throw new Error(`Received badly formatted Data URI`);
|
|
10
|
+
const base64 = toBase64(data);
|
|
11
|
+
const ab = new ArrayBuffer(base64.length);
|
|
12
|
+
const ia = new Uint8Array(ab);
|
|
13
|
+
for (let i = 0; i < base64.length; i++) ia[i] = base64.charCodeAt(i);
|
|
14
|
+
return new Blob([ab], { type: mimeType });
|
|
15
|
+
});
|
|
16
|
+
//#endregion
|
|
17
|
+
export { toBlob };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@discordkit/core",
|
|
3
3
|
"description": "Core utility functions for Discordkit",
|
|
4
|
-
"version": "
|
|
4
|
+
"version": "4.0.0",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Drake Costa <drake@saeris.gg> (https://saeris.gg)",
|
|
7
7
|
"keywords": [
|
|
@@ -20,21 +20,16 @@
|
|
|
20
20
|
"registry": "https://registry.npmjs.org"
|
|
21
21
|
},
|
|
22
22
|
"type": "module",
|
|
23
|
-
"main": "./dist/index.js",
|
|
24
|
-
"module": "./dist/index.js",
|
|
25
|
-
"types": "./dist/index.d.ts",
|
|
26
|
-
"imports": {
|
|
27
|
-
"#mock-utils": "../../scripts/mock-utils.ts",
|
|
28
|
-
"#test-utils": "../../scripts/test-utils.ts"
|
|
29
|
-
},
|
|
30
23
|
"exports": {
|
|
31
24
|
".": {
|
|
32
|
-
"
|
|
33
|
-
"
|
|
25
|
+
"development": "./src/index.ts",
|
|
26
|
+
"types": "./dist/index.d.mts",
|
|
27
|
+
"import": "./dist/index.mjs"
|
|
34
28
|
},
|
|
35
29
|
"./*": {
|
|
36
|
-
"
|
|
37
|
-
"
|
|
30
|
+
"development": "./src/*.ts",
|
|
31
|
+
"types": "./dist/*.d.mts",
|
|
32
|
+
"import": "./dist/*.mjs"
|
|
38
33
|
},
|
|
39
34
|
"./package.json": "./package.json"
|
|
40
35
|
},
|
|
@@ -42,18 +37,13 @@
|
|
|
42
37
|
"dist/**/*"
|
|
43
38
|
],
|
|
44
39
|
"sideEffects": false,
|
|
45
|
-
"scripts": {
|
|
46
|
-
"dev": "yarn build --watch --declarationmap",
|
|
47
|
-
"build": "tsc",
|
|
48
|
-
"lint": "eslint --cache"
|
|
49
|
-
},
|
|
50
40
|
"peerDependencies": {
|
|
51
|
-
"valibot": ">= 1.
|
|
52
|
-
},
|
|
53
|
-
"devDependencies": {
|
|
54
|
-
"typescript": "^5.8.3"
|
|
41
|
+
"valibot": ">= 1.4.0"
|
|
55
42
|
},
|
|
56
43
|
"dependencies": {
|
|
57
|
-
"type-fest": "^
|
|
44
|
+
"type-fest": "^5.6.0"
|
|
45
|
+
},
|
|
46
|
+
"devDependencies": {
|
|
47
|
+
"@discordkit/test-utils": "^0.0.0"
|
|
58
48
|
}
|
|
59
|
-
}
|
|
49
|
+
}
|
package/dist/index.d.ts
DELETED
package/dist/index.js
DELETED
package/dist/index.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,qBAAqB,CAAC;AACpC,cAAc,wBAAwB,CAAC"}
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
export declare const endpoint = "https://discord.com/api/v10/";
|
|
2
|
-
/** @internal */
|
|
3
|
-
export declare class DiscordSession {
|
|
4
|
-
#private;
|
|
5
|
-
endpoint: string;
|
|
6
|
-
get ready(): boolean;
|
|
7
|
-
constructor(authToken?: `${`Bot` | `Bearer`} ${string}` | null);
|
|
8
|
-
/**
|
|
9
|
-
* Clears the current session details, then attempts to set
|
|
10
|
-
* new session values
|
|
11
|
-
*/
|
|
12
|
-
setToken: (token: `${`Bot` | `Bearer`} ${string}`) => this;
|
|
13
|
-
clearSession: () => this;
|
|
14
|
-
getSession: () => string;
|
|
15
|
-
}
|
|
16
|
-
export declare const discord: DiscordSession;
|