@discordkit/core 3.2.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.ts → verifyKey.d.mts} +4 -1
- 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.js → 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 -25
- package/dist/requests/DiscordSession.js +0 -255
- 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 -8
- package/dist/requests/index.js +0 -9
- 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 -30
- 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/requests/verifyKey.js +0 -91
- package/dist/requests/verifyKey.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.d.ts +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/sleep.d.ts +0 -4
- package/dist/utils/sleep.js +0 -5
- package/dist/utils/sleep.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,63 @@
|
|
|
1
|
+
//#region src/requests/verifyKey.ts
|
|
2
|
+
/**
|
|
3
|
+
* Get the SubtleCrypto interface for the current environment
|
|
4
|
+
*/
|
|
5
|
+
const getSubtleCrypto = () => {
|
|
6
|
+
if (typeof globalThis.crypto !== `undefined`) return globalThis.crypto.subtle;
|
|
7
|
+
if (typeof global !== `undefined` && global.crypto?.subtle) return global.crypto.subtle;
|
|
8
|
+
if (typeof window !== `undefined` && window.crypto?.subtle) return window.crypto.subtle;
|
|
9
|
+
throw new Error(`SubtleCrypto is not available in this environment`);
|
|
10
|
+
};
|
|
11
|
+
const subtleCrypto = getSubtleCrypto();
|
|
12
|
+
/**
|
|
13
|
+
* Convert various input types to Uint8Array
|
|
14
|
+
*/
|
|
15
|
+
function valueToUint8Array(value, encoding) {
|
|
16
|
+
if (value instanceof Uint8Array) return value;
|
|
17
|
+
if (value instanceof ArrayBuffer) return new Uint8Array(value);
|
|
18
|
+
if (typeof Buffer !== `undefined` && Buffer.isBuffer(value)) return new Uint8Array(value);
|
|
19
|
+
if (typeof value === `string`) {
|
|
20
|
+
if (encoding === `hex`) {
|
|
21
|
+
const matches = value.match(/.{1,2}/g);
|
|
22
|
+
if (!matches) throw new Error(`Invalid hex string`);
|
|
23
|
+
return new Uint8Array(matches.map((byte) => parseInt(byte, 16)));
|
|
24
|
+
}
|
|
25
|
+
return new TextEncoder().encode(value);
|
|
26
|
+
}
|
|
27
|
+
throw new Error(`Unsupported value type`);
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Concatenate multiple Uint8Arrays into a single Uint8Array
|
|
31
|
+
*/
|
|
32
|
+
function concatUint8Arrays(...arrays) {
|
|
33
|
+
const totalLength = arrays.reduce((sum, arr) => sum + arr.length, 0);
|
|
34
|
+
const result = new Uint8Array(totalLength);
|
|
35
|
+
let offset = 0;
|
|
36
|
+
for (const arr of arrays) {
|
|
37
|
+
result.set(arr, offset);
|
|
38
|
+
offset += arr.length;
|
|
39
|
+
}
|
|
40
|
+
return result;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Validates a payload from Discord against its signature and key.
|
|
44
|
+
*
|
|
45
|
+
* @param rawBody - The raw payload data
|
|
46
|
+
* @param signature - The signature from the `X-Signature-Ed25519` header
|
|
47
|
+
* @param timestamp - The timestamp from the `X-Signature-Timestamp` header
|
|
48
|
+
* @param clientPublicKey - The public key from the Discord developer dashboard
|
|
49
|
+
* @returns Whether or not validation was successful
|
|
50
|
+
*/
|
|
51
|
+
async function verifyKey(rawBody, signature, timestamp, clientPublicKey) {
|
|
52
|
+
try {
|
|
53
|
+
return await subtleCrypto.verify({ name: `ed25519` }, typeof clientPublicKey === `string` ? await subtleCrypto.importKey(`raw`, Buffer.from(valueToUint8Array(clientPublicKey, `hex`)), {
|
|
54
|
+
name: `ed25519`,
|
|
55
|
+
namedCurve: `ed25519`
|
|
56
|
+
}, false, [`verify`]) : clientPublicKey, Buffer.from(valueToUint8Array(signature, `hex`)), Buffer.from(concatUint8Arrays(valueToUint8Array(timestamp), valueToUint8Array(rawBody))));
|
|
57
|
+
} catch (err) {
|
|
58
|
+
console.error(`Signature verification failed:`, err);
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
//#endregion
|
|
63
|
+
export { verifyKey };
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
//#region src/utils/isNonNullable.d.ts
|
|
2
|
+
type Maybe<T> = T | null | undefined;
|
|
1
3
|
/**
|
|
2
4
|
* Used to test whether a `Maybe` typed value is `null` or `undefined`.
|
|
3
5
|
*
|
|
@@ -16,5 +18,6 @@
|
|
|
16
18
|
* }
|
|
17
19
|
* ```
|
|
18
20
|
*/
|
|
19
|
-
|
|
20
|
-
//#
|
|
21
|
+
declare const isNonNullable: <T extends Maybe<unknown>>(val?: T) => val is NonNullable<T>;
|
|
22
|
+
//#endregion
|
|
23
|
+
export { Maybe, isNonNullable };
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
//#region src/utils/isNonNullable.ts
|
|
2
|
+
/**
|
|
3
|
+
* Used to test whether a `Maybe` typed value is `null` or `undefined`.
|
|
4
|
+
*
|
|
5
|
+
* When called, the given value's type is narrowed to `NonNullable<T>`.
|
|
6
|
+
*
|
|
7
|
+
* ### Example Usage:
|
|
8
|
+
*
|
|
9
|
+
* ```ts
|
|
10
|
+
* const fn = (str: Maybe<string>) => {
|
|
11
|
+
* if (!isNonNullable(str)) {
|
|
12
|
+
* // typeof str = null | undefined
|
|
13
|
+
* // ...
|
|
14
|
+
* }
|
|
15
|
+
* // typeof str = string
|
|
16
|
+
* // ...
|
|
17
|
+
* }
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
const isNonNullable = (val) => typeof val !== `undefined` && val !== null;
|
|
21
|
+
//#endregion
|
|
22
|
+
export { isNonNullable };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { isObject } from "./isObject.mjs";
|
|
2
|
+
import { toCamelCase } from "./toCamelCase.mjs";
|
|
3
|
+
//#region src/utils/toCamelKeys.ts
|
|
4
|
+
const toCamelKeys = (o) => {
|
|
5
|
+
if (Array.isArray(o)) return o.map(toCamelKeys);
|
|
6
|
+
else if (isObject(o)) return Object.entries(o).reduce((acc, [key, value]) => {
|
|
7
|
+
acc[toCamelCase(key)] = toCamelKeys(value);
|
|
8
|
+
return acc;
|
|
9
|
+
}, {});
|
|
10
|
+
return o;
|
|
11
|
+
};
|
|
12
|
+
//#endregion
|
|
13
|
+
export { toCamelKeys };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { toSnakeCase } from "./toSnakeCase.mjs";
|
|
2
|
+
import { isObject } from "./isObject.mjs";
|
|
3
|
+
//#region src/utils/toSnakeKeys.ts
|
|
4
|
+
const toSnakeKeys = (o) => {
|
|
5
|
+
if (Array.isArray(o)) return o.map(toSnakeKeys);
|
|
6
|
+
else if (isObject(o)) return Object.entries(o).reduce((acc, [key, value]) => {
|
|
7
|
+
acc[toSnakeCase(key)] = toSnakeKeys(value);
|
|
8
|
+
return acc;
|
|
9
|
+
}, {});
|
|
10
|
+
return o;
|
|
11
|
+
};
|
|
12
|
+
//#endregion
|
|
13
|
+
export { toSnakeKeys };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { bitfield } from "./bitfield.mjs";
|
|
2
|
+
import * as v from "valibot";
|
|
3
|
+
|
|
4
|
+
//#region src/validations/asDigits.d.ts
|
|
5
|
+
/**
|
|
6
|
+
* Transforms a `bitfield` schema into a numeric string
|
|
7
|
+
*
|
|
8
|
+
* @__NO_SIDE_EFFECTS__
|
|
9
|
+
*/
|
|
10
|
+
declare const asDigits: (schema: ReturnType<typeof bitfield>) => v.GenericSchema<string>;
|
|
11
|
+
//#endregion
|
|
12
|
+
export { asDigits };
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import * as v from "valibot";
|
|
2
|
+
//#region src/validations/asDigits.ts
|
|
3
|
+
/**
|
|
4
|
+
* Transforms a `bitfield` schema into a numeric string
|
|
5
|
+
*
|
|
6
|
+
* @__NO_SIDE_EFFECTS__
|
|
7
|
+
*/
|
|
8
|
+
const asDigits = (schema) => v.pipe(schema, v.transform((val) => val.toString()), v.digits());
|
|
9
|
+
//#endregion
|
|
10
|
+
export { asDigits };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { bitfield } from "./bitfield.mjs";
|
|
2
|
+
import * as v from "valibot";
|
|
3
|
+
|
|
4
|
+
//#region src/validations/asInteger.d.ts
|
|
5
|
+
/**
|
|
6
|
+
* Transforms a `bitfield` schema into an integer
|
|
7
|
+
*
|
|
8
|
+
* @__NO_SIDE_EFFECTS__
|
|
9
|
+
*/
|
|
10
|
+
declare const asInteger: (schema: ReturnType<typeof bitfield>) => v.GenericSchema<number>;
|
|
11
|
+
//#endregion
|
|
12
|
+
export { asInteger };
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import * as v from "valibot";
|
|
2
|
+
//#region src/validations/asInteger.ts
|
|
3
|
+
/**
|
|
4
|
+
* Transforms a `bitfield` schema into an integer
|
|
5
|
+
*
|
|
6
|
+
* @__NO_SIDE_EFFECTS__
|
|
7
|
+
*/
|
|
8
|
+
const asInteger = (schema) => v.pipe(schema, v.transform((val) => parseInt(val.toString(), 10)), v.integer());
|
|
9
|
+
//#endregion
|
|
10
|
+
export { asInteger };
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { CustomSchema, SchemaWithPipe, TitleAction } from "valibot";
|
|
2
|
+
|
|
3
|
+
//#region src/validations/bitfield.d.ts
|
|
4
|
+
interface Flags {
|
|
5
|
+
[key: string]: number | bigint | string;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Given an enum of bitwise flags, creates a new schema that
|
|
9
|
+
* can validate a [bitfield](https://en.wikipedia.org/wiki/Bit_field)
|
|
10
|
+
* numeric value (a data structure for efficiently serializing a
|
|
11
|
+
* group of boolean values).
|
|
12
|
+
*
|
|
13
|
+
* @__NO_SIDE_EFFECTS__
|
|
14
|
+
*/
|
|
15
|
+
declare const bitfield: <TName extends string>(/** A name to differentiate this custom schema */
|
|
16
|
+
|
|
17
|
+
name: TName, /** An enum of bitwise flags */
|
|
18
|
+
|
|
19
|
+
flags: Flags, /** An optional error message to display in the event an invalid value is parsed */
|
|
20
|
+
|
|
21
|
+
message?: string) => SchemaWithPipe<readonly [CustomSchema<string | number | bigint, string>, TitleAction<string | number | bigint, TName>]>;
|
|
22
|
+
//#endregion
|
|
23
|
+
export { Flags, bitfield };
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { isNonNullable } from "../utils/isNonNullable.mjs";
|
|
2
|
+
import { isNumericString } from "../utils/isNumericString.mjs";
|
|
3
|
+
import { custom, pipe, title } from "valibot";
|
|
4
|
+
//#region src/validations/bitfield.ts
|
|
5
|
+
/**
|
|
6
|
+
* Given an enum of bitwise flags, creates a new schema that
|
|
7
|
+
* can validate a [bitfield](https://en.wikipedia.org/wiki/Bit_field)
|
|
8
|
+
* numeric value (a data structure for efficiently serializing a
|
|
9
|
+
* group of boolean values).
|
|
10
|
+
*
|
|
11
|
+
* @__NO_SIDE_EFFECTS__
|
|
12
|
+
*/
|
|
13
|
+
const bitfield = (name, flags, message = `Invalid Bitfield`) => {
|
|
14
|
+
const flagValues = Object.values(flags).filter((flag) => !isNaN(Number(flag)));
|
|
15
|
+
if (!flagValues.every((flag) => typeof flag === typeof flagValues[0])) throw new Error(`Provided Flags enum must contain values of the same type`);
|
|
16
|
+
const mask = flagValues.reduce((total, flag) => total | BigInt(flag), 0n);
|
|
17
|
+
return pipe(custom((val) => isNonNullable(val) && (typeof val === `number` || typeof val === `bigint` || isNumericString(val)) ? (BigInt(val) & mask) === BigInt(val) : false, message), title(name));
|
|
18
|
+
};
|
|
19
|
+
//#endregion
|
|
20
|
+
export { bitfield };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import * as v from "valibot";
|
|
2
|
+
|
|
3
|
+
//#region src/validations/boundedArray.d.ts
|
|
4
|
+
/** A non-empty array with a length within the given bounds
|
|
5
|
+
*
|
|
6
|
+
* @__NO_SIDE_EFFECTS__
|
|
7
|
+
*/
|
|
8
|
+
declare const boundedArray: <TItem>(items: v.GenericSchema<TItem>, req?: number | {
|
|
9
|
+
min?: number;
|
|
10
|
+
max?: number;
|
|
11
|
+
}) => v.GenericSchema<TItem[]>;
|
|
12
|
+
//#endregion
|
|
13
|
+
export { boundedArray };
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import * as v from "valibot";
|
|
2
|
+
//#region src/validations/boundedArray.ts
|
|
3
|
+
/** A non-empty array with a length within the given bounds
|
|
4
|
+
*
|
|
5
|
+
* @__NO_SIDE_EFFECTS__
|
|
6
|
+
*/
|
|
7
|
+
const boundedArray = (items, req = {}) => v.message(typeof req === `number` ? v.pipe(v.array(items), v.length(req)) : typeof req.max === `number` ? v.pipe(v.array(items), v.minLength(req.min ?? 1), v.maxLength(req.max)) : v.pipe(v.array(items), v.minLength(req.min ?? 1)), (issue) => `Expected an array with a legnth ${typeof req === `number` ? req : `>= ${req.min ?? 0}${req.max ? `&& <= ${req.max}` : ``}`}, received has length: ${issue.received.length}`);
|
|
8
|
+
//#endregion
|
|
9
|
+
export { boundedArray };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import * as v from "valibot";
|
|
2
|
+
|
|
3
|
+
//#region src/validations/boundedInteger.d.ts
|
|
4
|
+
/** An integer with a value within the given bounds
|
|
5
|
+
*
|
|
6
|
+
* @__NO_SIDE_EFFECTS__
|
|
7
|
+
*/
|
|
8
|
+
declare const boundedInteger: (req?: number | {
|
|
9
|
+
min?: number;
|
|
10
|
+
max?: number;
|
|
11
|
+
}) => v.GenericSchema<number>;
|
|
12
|
+
//#endregion
|
|
13
|
+
export { boundedInteger };
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import * as v from "valibot";
|
|
2
|
+
//#region src/validations/boundedInteger.ts
|
|
3
|
+
/** An integer with a value within the given bounds
|
|
4
|
+
*
|
|
5
|
+
* @__NO_SIDE_EFFECTS__
|
|
6
|
+
*/
|
|
7
|
+
const boundedInteger = (req = {}) => v.message(typeof req === `number` ? v.pipe(v.number(), v.integer(), v.value(req)) : typeof req.max === `number` ? v.pipe(v.number(), v.integer(), v.minValue(req.min ?? 0), v.maxValue(req.max)) : v.pipe(v.number(), v.integer(), v.minValue(req.min ?? 0)), (issue) => `Expected an integer with a value ${typeof req === `number` ? `of ${req}` : `>= ${req.min ?? 0}${req.max ? `&& <= ${req.max}` : ``}`}, received has value: ${issue.received.length}`);
|
|
8
|
+
//#endregion
|
|
9
|
+
export { boundedInteger };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import * as v from "valibot";
|
|
2
|
+
|
|
3
|
+
//#region src/validations/boundedString.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* A non-empty string with a length within the given bounds.
|
|
6
|
+
*
|
|
7
|
+
* @__NO_SIDE_EFFECTS__
|
|
8
|
+
*/
|
|
9
|
+
declare const boundedString: (req?: number | {
|
|
10
|
+
min?: number;
|
|
11
|
+
max?: number;
|
|
12
|
+
}) => v.GenericSchema<string>;
|
|
13
|
+
//#endregion
|
|
14
|
+
export { boundedString };
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import * as v from "valibot";
|
|
2
|
+
//#region src/validations/boundedString.ts
|
|
3
|
+
/**
|
|
4
|
+
* A non-empty string with a length within the given bounds.
|
|
5
|
+
*
|
|
6
|
+
* @__NO_SIDE_EFFECTS__
|
|
7
|
+
*/
|
|
8
|
+
const boundedString = (req = {}) => v.message(typeof req === `number` ? v.pipe(v.string(), v.length(req)) : typeof req.max === `number` ? v.pipe(v.string(), v.minLength(req.min ?? 1), v.maxLength(req.max)) : v.pipe(v.string(), v.minLength(req.min ?? 1)), (issue) => `Expected a string with a legnth ${typeof req === `number` ? req : `>= ${req.min ?? 0}${req.max ? `&& <= ${req.max}` : ``}`}, received has length: ${issue.received.length}`);
|
|
9
|
+
//#endregion
|
|
10
|
+
export { boundedString };
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import * as v from "valibot";
|
|
2
|
+
|
|
3
|
+
//#region src/validations/datauri.d.ts
|
|
4
|
+
declare const datauriRegex: RegExp;
|
|
5
|
+
/**
|
|
6
|
+
* Extracts metadata from a given Data URI such as it's
|
|
7
|
+
* MIME type, params, and encoding
|
|
8
|
+
*
|
|
9
|
+
* Returns an empty object when given an invalid Data URI
|
|
10
|
+
*/
|
|
11
|
+
declare const extractDataURIMetadata: (val: string) => Partial<{
|
|
12
|
+
mediaType: string;
|
|
13
|
+
mimeType: `${string}/${string}`;
|
|
14
|
+
params: string;
|
|
15
|
+
encoding: string;
|
|
16
|
+
data: string;
|
|
17
|
+
}>;
|
|
18
|
+
declare const toBase64: (data: string) => string;
|
|
19
|
+
/**
|
|
20
|
+
* Validates that a string is a [data URI scheme](https://en.wikipedia.org/wiki/Data_URI_scheme)
|
|
21
|
+
*/
|
|
22
|
+
declare const datauri: v.GenericSchema<string>;
|
|
23
|
+
//#endregion
|
|
24
|
+
export { datauri, datauriRegex, extractDataURIMetadata, toBase64 };
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import * as v from "valibot";
|
|
2
|
+
//#region src/validations/datauri.ts
|
|
3
|
+
const datauriRegex = /^data:((?<mediaType>(?<mimeType>[a-z]+\/[a-z0-9-+.]+)(?<params>;[a-z0-9-.!#$%*+.{}|~`]+=[a-z0-9-.!#$%*+.{}()_|~`]+)*))?(?<encoding>;base64)?,(?<data>[a-z0-9!$&',()*+;=\-._~:@/?%\s<>]*?)$/i;
|
|
4
|
+
/**
|
|
5
|
+
* Extracts metadata from a given Data URI such as it's
|
|
6
|
+
* MIME type, params, and encoding
|
|
7
|
+
*
|
|
8
|
+
* Returns an empty object when given an invalid Data URI
|
|
9
|
+
*/
|
|
10
|
+
const extractDataURIMetadata = (val) => datauriRegex.exec(val)?.groups ?? {};
|
|
11
|
+
const toBase64 = (data) => typeof Buffer !== `undefined` ? Buffer.from(data, `base64`).toString() : atob(btoa(String.fromCharCode(...new TextEncoder().encode(data))).replace(/\+/g, `-`).replace(/\//g, `_`).replace(/=/g, ``));
|
|
12
|
+
/**
|
|
13
|
+
* Validates that a string is a [data URI scheme](https://en.wikipedia.org/wiki/Data_URI_scheme)
|
|
14
|
+
*/
|
|
15
|
+
const datauri = v.pipe(v.custom((val) => typeof val === `string` && val.length > 0 && datauriRegex.test(val), `Invalid Data URI`), v.title(`datauri`));
|
|
16
|
+
//#endregion
|
|
17
|
+
export { datauri, datauriRegex, extractDataURIMetadata, toBase64 };
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import * as v from "valibot";
|
|
2
|
+
|
|
3
|
+
//#region src/validations/fileUpload.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* The shape of a single file upload — used wherever a Discord endpoint
|
|
6
|
+
* accepts an attached file via `multipart/form-data`.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```ts
|
|
10
|
+
* await executeWebhook({
|
|
11
|
+
* webhook: "...",
|
|
12
|
+
* token: "...",
|
|
13
|
+
* body: {
|
|
14
|
+
* content: "Look at this!",
|
|
15
|
+
* files: [
|
|
16
|
+
* { filename: "photo.png", content: new Blob([bytes], { type: "image/png" }) }
|
|
17
|
+
* ]
|
|
18
|
+
* }
|
|
19
|
+
* });
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
interface FileUpload {
|
|
23
|
+
/** Filename including extension. Required for the multipart `Content-Disposition` header. */
|
|
24
|
+
filename: string;
|
|
25
|
+
/** The binary content. Accepts a web-standard `Blob` or `File`. */
|
|
26
|
+
content: Blob;
|
|
27
|
+
/** Optional MIME type. If omitted, taken from the `Blob`'s `type` property. */
|
|
28
|
+
contentType?: string;
|
|
29
|
+
}
|
|
30
|
+
/** Type guard — true if `val` looks like a {@link FileUpload}. */
|
|
31
|
+
declare const isFileUpload: (val: unknown) => val is FileUpload;
|
|
32
|
+
/**
|
|
33
|
+
* Valibot schema for a single file upload. Validates the shape and
|
|
34
|
+
* surfaces a clean TypeScript type to consumers.
|
|
35
|
+
*/
|
|
36
|
+
declare const fileUpload: v.GenericSchema<FileUpload>;
|
|
37
|
+
/**
|
|
38
|
+
* Walks a value and returns all {@link FileUpload}s within it, along
|
|
39
|
+
* with the path each one occupies.
|
|
40
|
+
*/
|
|
41
|
+
declare const collectFileUploads: (val: unknown, path?: ReadonlyArray<string | number>) => ReadonlyArray<{
|
|
42
|
+
readonly path: ReadonlyArray<string | number>;
|
|
43
|
+
readonly file: FileUpload;
|
|
44
|
+
}>;
|
|
45
|
+
/**
|
|
46
|
+
* Given a body that contains one or more {@link FileUpload}s, produce
|
|
47
|
+
* a `FormData` payload Discord can consume.
|
|
48
|
+
*
|
|
49
|
+
* Files are appended as `files[n]` parts; the rest of the body is
|
|
50
|
+
* snake_cased and JSON-stringified into a `payload_json` part.
|
|
51
|
+
*
|
|
52
|
+
* Each FileUpload in the body is replaced by its attachment placeholder
|
|
53
|
+
* `{ id: n }` so that an `attachments` array (if present) can reference
|
|
54
|
+
* uploads by index. Discord matches these placeholders to the
|
|
55
|
+
* corresponding `files[n]` parts.
|
|
56
|
+
*/
|
|
57
|
+
declare const toMultipartBody: (body: unknown, toSnakeKeys: (val: object) => unknown) => FormData;
|
|
58
|
+
/**
|
|
59
|
+
* Sentinel symbol used to mark a validated body as multipart-eligible.
|
|
60
|
+
* Read by the request layer at serialization time.
|
|
61
|
+
*
|
|
62
|
+
* The marker is attached as a non-enumerable property on the parsed
|
|
63
|
+
* body so it doesn't leak into JSON serialization or the TypeScript
|
|
64
|
+
* output type.
|
|
65
|
+
*/
|
|
66
|
+
declare const MULTIPART_MARKER: unique symbol;
|
|
67
|
+
/**
|
|
68
|
+
* Returns true if `body` should be serialized as `multipart/form-data`.
|
|
69
|
+
*
|
|
70
|
+
* Two signals trigger multipart serialization:
|
|
71
|
+
*
|
|
72
|
+
* 1. **Schema-tagged** — the body was parsed by a {@link multipart}-wrapped
|
|
73
|
+
* schema and at least one {@link FileUpload} was present. The wrapper
|
|
74
|
+
* stamps a non-enumerable {@link MULTIPART_MARKER} on the parsed value.
|
|
75
|
+
* This is the fast path for the validated flow (e.g., `toValidated`).
|
|
76
|
+
*
|
|
77
|
+
* 2. **Value-detected** — the body contains a {@link FileUpload} anywhere
|
|
78
|
+
* in its shape, regardless of whether it was validated. This safety net
|
|
79
|
+
* keeps consumers who bypass validation from silently dropping their
|
|
80
|
+
* files into a JSON body where `Blob` would serialize as `{}`.
|
|
81
|
+
*
|
|
82
|
+
* Either signal switches the request to multipart.
|
|
83
|
+
*/
|
|
84
|
+
declare const shouldSerializeAsMultipart: (body: unknown) => boolean;
|
|
85
|
+
/**
|
|
86
|
+
* Wraps an object schema to mark its body as a potential multipart payload.
|
|
87
|
+
*
|
|
88
|
+
* Use {@link multipart} instead of `v.object` whenever an endpoint's body
|
|
89
|
+
* may contain one or more {@link fileUpload} fields (or arrays of them,
|
|
90
|
+
* or optional uploads). The wrapper validates the same shape as
|
|
91
|
+
* `v.object(entries)`. At validation time, the wrapper inspects the
|
|
92
|
+
* parsed body for {@link FileUpload}s. If any are present, it stamps a
|
|
93
|
+
* non-enumerable {@link MULTIPART_MARKER} on the result; the request
|
|
94
|
+
* layer reads that marker at serialization time to choose between
|
|
95
|
+
* `multipart/form-data` (marker present) and `application/json`
|
|
96
|
+
* (marker absent).
|
|
97
|
+
*
|
|
98
|
+
* Pass `{ partial: true }` for endpoints where every field is optional —
|
|
99
|
+
* the wrapper applies `v.partial(...)` to the inner object before piping
|
|
100
|
+
* the transform. `v.partial` itself can't wrap the piped result (it only
|
|
101
|
+
* accepts plain object schemas), so the option lives here.
|
|
102
|
+
*
|
|
103
|
+
* @example
|
|
104
|
+
* ```ts
|
|
105
|
+
* import { multipart, fileUpload } from "@discordkit/core";
|
|
106
|
+
* import * as v from "valibot";
|
|
107
|
+
*
|
|
108
|
+
* export const updateAvatarSchema = v.object({
|
|
109
|
+
* body: multipart({
|
|
110
|
+
* avatar: v.exactOptional(fileUpload),
|
|
111
|
+
* bio: v.exactOptional(v.string())
|
|
112
|
+
* })
|
|
113
|
+
* });
|
|
114
|
+
*
|
|
115
|
+
* // Every field optional:
|
|
116
|
+
* export const editMessageSchema = v.object({
|
|
117
|
+
* body: multipart(
|
|
118
|
+
* { content: v.string(), files: v.array(fileUpload) },
|
|
119
|
+
* { partial: true }
|
|
120
|
+
* )
|
|
121
|
+
* });
|
|
122
|
+
* ```
|
|
123
|
+
*/
|
|
124
|
+
declare function multipart<TEntries extends v.ObjectEntries>(entries: TEntries): v.GenericSchema<v.InferOutput<v.ObjectSchema<TEntries, undefined>>>;
|
|
125
|
+
declare function multipart<TEntries extends v.ObjectEntries>(entries: TEntries, options: {
|
|
126
|
+
partial: true;
|
|
127
|
+
}): v.GenericSchema<v.InferOutput<v.SchemaWithPartial<v.ObjectSchema<TEntries, undefined>, undefined>>>;
|
|
128
|
+
//#endregion
|
|
129
|
+
export { FileUpload, MULTIPART_MARKER, collectFileUploads, fileUpload, isFileUpload, multipart, shouldSerializeAsMultipart, toMultipartBody };
|