@minesa-org/mini-interaction 0.1.2 → 0.1.3
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/database/MiniDataBuilder.d.ts +62 -0
- package/dist/database/MiniDataBuilder.js +98 -0
- package/dist/database/MiniDatabase.d.ts +61 -0
- package/dist/database/MiniDatabase.js +233 -0
- package/dist/database/MiniDatabaseBuilder.d.ts +72 -0
- package/dist/database/MiniDatabaseBuilder.js +103 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +5 -0
- package/dist/oauth/DiscordOAuth.d.ts +49 -0
- package/dist/oauth/DiscordOAuth.js +110 -0
- package/dist/oauth/OAuthTokenStorage.d.ts +31 -0
- package/dist/oauth/OAuthTokenStorage.js +109 -0
- package/package.json +1 -1
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { JSONEncodable } from "../builders/shared.js";
|
|
2
|
+
/**
|
|
3
|
+
* Represents a field in the data schema.
|
|
4
|
+
*/
|
|
5
|
+
export interface DataField {
|
|
6
|
+
name: string;
|
|
7
|
+
type: "string" | "number" | "boolean" | "object" | "array";
|
|
8
|
+
required?: boolean;
|
|
9
|
+
default?: unknown;
|
|
10
|
+
description?: string;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Builder for defining data schemas in MiniDatabase.
|
|
14
|
+
* Provides a fluent API for developers to define their data structure.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```typescript
|
|
18
|
+
* const userSchema = new MiniDataBuilder()
|
|
19
|
+
* .addField("userId", "string", { required: true })
|
|
20
|
+
* .addField("username", "string", { required: true })
|
|
21
|
+
* .addField("coins", "number", { default: 0 })
|
|
22
|
+
* .addField("metadata", "object", { default: {} });
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
export declare class MiniDataBuilder implements JSONEncodable<Record<string, DataField>> {
|
|
26
|
+
private fields;
|
|
27
|
+
/**
|
|
28
|
+
* Adds a field to the data schema.
|
|
29
|
+
*/
|
|
30
|
+
addField(name: string, type: "string" | "number" | "boolean" | "object" | "array", options?: {
|
|
31
|
+
required?: boolean;
|
|
32
|
+
default?: unknown;
|
|
33
|
+
description?: string;
|
|
34
|
+
}): this;
|
|
35
|
+
/**
|
|
36
|
+
* Removes a field from the schema.
|
|
37
|
+
*/
|
|
38
|
+
removeField(name: string): this;
|
|
39
|
+
/**
|
|
40
|
+
* Gets a specific field definition.
|
|
41
|
+
*/
|
|
42
|
+
getField(name: string): DataField | undefined;
|
|
43
|
+
/**
|
|
44
|
+
* Gets all fields in the schema.
|
|
45
|
+
*/
|
|
46
|
+
getFields(): DataField[];
|
|
47
|
+
/**
|
|
48
|
+
* Validates data against the schema.
|
|
49
|
+
*/
|
|
50
|
+
validate(data: Record<string, unknown>): {
|
|
51
|
+
valid: boolean;
|
|
52
|
+
errors: string[];
|
|
53
|
+
};
|
|
54
|
+
/**
|
|
55
|
+
* Applies default values to data.
|
|
56
|
+
*/
|
|
57
|
+
applyDefaults(data: Record<string, unknown>): Record<string, unknown>;
|
|
58
|
+
/**
|
|
59
|
+
* Serializes the schema to JSON.
|
|
60
|
+
*/
|
|
61
|
+
toJSON(): Record<string, DataField>;
|
|
62
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Builder for defining data schemas in MiniDatabase.
|
|
3
|
+
* Provides a fluent API for developers to define their data structure.
|
|
4
|
+
*
|
|
5
|
+
* @example
|
|
6
|
+
* ```typescript
|
|
7
|
+
* const userSchema = new MiniDataBuilder()
|
|
8
|
+
* .addField("userId", "string", { required: true })
|
|
9
|
+
* .addField("username", "string", { required: true })
|
|
10
|
+
* .addField("coins", "number", { default: 0 })
|
|
11
|
+
* .addField("metadata", "object", { default: {} });
|
|
12
|
+
* ```
|
|
13
|
+
*/
|
|
14
|
+
export class MiniDataBuilder {
|
|
15
|
+
fields = new Map();
|
|
16
|
+
/**
|
|
17
|
+
* Adds a field to the data schema.
|
|
18
|
+
*/
|
|
19
|
+
addField(name, type, options) {
|
|
20
|
+
this.fields.set(name, {
|
|
21
|
+
name,
|
|
22
|
+
type,
|
|
23
|
+
required: options?.required ?? false,
|
|
24
|
+
default: options?.default,
|
|
25
|
+
description: options?.description,
|
|
26
|
+
});
|
|
27
|
+
return this;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Removes a field from the schema.
|
|
31
|
+
*/
|
|
32
|
+
removeField(name) {
|
|
33
|
+
this.fields.delete(name);
|
|
34
|
+
return this;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Gets a specific field definition.
|
|
38
|
+
*/
|
|
39
|
+
getField(name) {
|
|
40
|
+
return this.fields.get(name);
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Gets all fields in the schema.
|
|
44
|
+
*/
|
|
45
|
+
getFields() {
|
|
46
|
+
return Array.from(this.fields.values());
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Validates data against the schema.
|
|
50
|
+
*/
|
|
51
|
+
validate(data) {
|
|
52
|
+
const errors = [];
|
|
53
|
+
for (const field of this.fields.values()) {
|
|
54
|
+
const value = data[field.name];
|
|
55
|
+
// Check required fields
|
|
56
|
+
if (field.required && (value === undefined || value === null)) {
|
|
57
|
+
errors.push(`Field "${field.name}" is required`);
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
// Check type if value exists
|
|
61
|
+
if (value !== undefined && value !== null) {
|
|
62
|
+
const actualType = Array.isArray(value) ? "array" : typeof value;
|
|
63
|
+
if (actualType !== field.type) {
|
|
64
|
+
errors.push(`Field "${field.name}" must be of type "${field.type}", got "${actualType}"`);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return {
|
|
69
|
+
valid: errors.length === 0,
|
|
70
|
+
errors,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Applies default values to data.
|
|
75
|
+
*/
|
|
76
|
+
applyDefaults(data) {
|
|
77
|
+
const result = { ...data };
|
|
78
|
+
for (const field of this.fields.values()) {
|
|
79
|
+
if (result[field.name] === undefined && field.default !== undefined) {
|
|
80
|
+
result[field.name] =
|
|
81
|
+
typeof field.default === "object"
|
|
82
|
+
? JSON.parse(JSON.stringify(field.default))
|
|
83
|
+
: field.default;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return result;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Serializes the schema to JSON.
|
|
90
|
+
*/
|
|
91
|
+
toJSON() {
|
|
92
|
+
const result = {};
|
|
93
|
+
for (const [key, field] of this.fields) {
|
|
94
|
+
result[key] = field;
|
|
95
|
+
}
|
|
96
|
+
return result;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type { MiniDataBuilder } from "./MiniDataBuilder.js";
|
|
2
|
+
import type { DatabaseConfig } from "./MiniDatabaseBuilder.js";
|
|
3
|
+
/**
|
|
4
|
+
* MiniDatabase provides async data storage with support for JSON and MongoDB backends.
|
|
5
|
+
* Designed to work seamlessly with Vercel and serverless environments.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```typescript
|
|
9
|
+
* const db = new MiniDatabase(config, schema);
|
|
10
|
+
*
|
|
11
|
+
* // Get data
|
|
12
|
+
* const user = await db.get("user123");
|
|
13
|
+
*
|
|
14
|
+
* // Set data
|
|
15
|
+
* await db.set("user123", { username: "john", coins: 100 });
|
|
16
|
+
*
|
|
17
|
+
* // Update data
|
|
18
|
+
* await db.update("user123", { coins: 150 });
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
export declare class MiniDatabase {
|
|
22
|
+
private config;
|
|
23
|
+
private schema?;
|
|
24
|
+
private mongoClient?;
|
|
25
|
+
private mongoDb?;
|
|
26
|
+
private mongoCollection?;
|
|
27
|
+
private initPromise?;
|
|
28
|
+
constructor(config: DatabaseConfig, schema?: MiniDataBuilder);
|
|
29
|
+
/**
|
|
30
|
+
* Initializes the database connection.
|
|
31
|
+
*/
|
|
32
|
+
private initialize;
|
|
33
|
+
/**
|
|
34
|
+
* Initializes MongoDB connection.
|
|
35
|
+
*/
|
|
36
|
+
private initializeMongoDB;
|
|
37
|
+
/**
|
|
38
|
+
* Initializes JSON file storage.
|
|
39
|
+
*/
|
|
40
|
+
private initializeJSON;
|
|
41
|
+
/**
|
|
42
|
+
* Gets data by key.
|
|
43
|
+
*/
|
|
44
|
+
get(key: string): Promise<Record<string, unknown> | null>;
|
|
45
|
+
/**
|
|
46
|
+
* Sets data by key (overwrites existing data).
|
|
47
|
+
*/
|
|
48
|
+
set(key: string, data: Record<string, unknown>): Promise<boolean>;
|
|
49
|
+
/**
|
|
50
|
+
* Updates specific fields in data (merges with existing data).
|
|
51
|
+
*/
|
|
52
|
+
update(key: string, updates: Record<string, unknown>): Promise<boolean>;
|
|
53
|
+
/**
|
|
54
|
+
* Deletes data by key.
|
|
55
|
+
*/
|
|
56
|
+
delete(key: string): Promise<boolean>;
|
|
57
|
+
/**
|
|
58
|
+
* Closes the database connection (for MongoDB).
|
|
59
|
+
*/
|
|
60
|
+
close(): Promise<void>;
|
|
61
|
+
}
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
import { promises as fs } from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
/**
|
|
4
|
+
* MiniDatabase provides async data storage with support for JSON and MongoDB backends.
|
|
5
|
+
* Designed to work seamlessly with Vercel and serverless environments.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```typescript
|
|
9
|
+
* const db = new MiniDatabase(config, schema);
|
|
10
|
+
*
|
|
11
|
+
* // Get data
|
|
12
|
+
* const user = await db.get("user123");
|
|
13
|
+
*
|
|
14
|
+
* // Set data
|
|
15
|
+
* await db.set("user123", { username: "john", coins: 100 });
|
|
16
|
+
*
|
|
17
|
+
* // Update data
|
|
18
|
+
* await db.update("user123", { coins: 150 });
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
export class MiniDatabase {
|
|
22
|
+
config;
|
|
23
|
+
schema;
|
|
24
|
+
mongoClient;
|
|
25
|
+
mongoDb;
|
|
26
|
+
mongoCollection;
|
|
27
|
+
initPromise;
|
|
28
|
+
constructor(config, schema) {
|
|
29
|
+
this.config = config;
|
|
30
|
+
this.schema = schema;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Initializes the database connection.
|
|
34
|
+
*/
|
|
35
|
+
async initialize() {
|
|
36
|
+
if (this.initPromise) {
|
|
37
|
+
return this.initPromise;
|
|
38
|
+
}
|
|
39
|
+
this.initPromise = (async () => {
|
|
40
|
+
if (this.config.type === "mongodb") {
|
|
41
|
+
await this.initializeMongoDB();
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
await this.initializeJSON();
|
|
45
|
+
}
|
|
46
|
+
})();
|
|
47
|
+
return this.initPromise;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Initializes MongoDB connection.
|
|
51
|
+
*/
|
|
52
|
+
async initializeMongoDB() {
|
|
53
|
+
try {
|
|
54
|
+
let MongoClient;
|
|
55
|
+
try {
|
|
56
|
+
// @ts-ignore - MongoDB is optional
|
|
57
|
+
const mongoModule = await import("mongodb");
|
|
58
|
+
MongoClient = mongoModule.MongoClient;
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
throw new Error("MongoDB driver not installed. Install it with: npm install mongodb");
|
|
62
|
+
}
|
|
63
|
+
if (!this.config.mongoUri) {
|
|
64
|
+
throw new Error("MongoDB URI is required");
|
|
65
|
+
}
|
|
66
|
+
this.mongoClient = new MongoClient(this.config.mongoUri, {
|
|
67
|
+
maxPoolSize: 5,
|
|
68
|
+
});
|
|
69
|
+
await this.mongoClient.connect();
|
|
70
|
+
this.mongoDb = this.mongoClient.db(this.config.dbName || "minidb");
|
|
71
|
+
this.mongoCollection = this.mongoDb.collection(this.config.collectionName || "data");
|
|
72
|
+
console.log("✅ [MiniDatabase] Connected to MongoDB");
|
|
73
|
+
}
|
|
74
|
+
catch (err) {
|
|
75
|
+
console.error("❌ [MiniDatabase] Failed to connect to MongoDB:", err);
|
|
76
|
+
throw err;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Initializes JSON file storage.
|
|
81
|
+
*/
|
|
82
|
+
async initializeJSON() {
|
|
83
|
+
try {
|
|
84
|
+
const dataPath = this.config.dataPath || "./data";
|
|
85
|
+
await fs.mkdir(dataPath, { recursive: true });
|
|
86
|
+
console.log("✅ [MiniDatabase] JSON storage initialized at", dataPath);
|
|
87
|
+
}
|
|
88
|
+
catch (err) {
|
|
89
|
+
console.error("❌ [MiniDatabase] Failed to initialize JSON storage:", err);
|
|
90
|
+
throw err;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Gets data by key.
|
|
95
|
+
*/
|
|
96
|
+
async get(key) {
|
|
97
|
+
await this.initialize();
|
|
98
|
+
try {
|
|
99
|
+
if (this.config.type === "mongodb") {
|
|
100
|
+
const doc = await this.mongoCollection.findOne({ _id: key });
|
|
101
|
+
return doc ? { ...doc, _id: undefined } : null;
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
const filePath = path.join(this.config.dataPath || "./data", `${key}.json`);
|
|
105
|
+
try {
|
|
106
|
+
const data = await fs.readFile(filePath, "utf-8");
|
|
107
|
+
return JSON.parse(data);
|
|
108
|
+
}
|
|
109
|
+
catch (err) {
|
|
110
|
+
if (err.code === "ENOENT") {
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
throw err;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
catch (err) {
|
|
118
|
+
console.error(`❌ [MiniDatabase] Failed to get data for key "${key}":`, err);
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Sets data by key (overwrites existing data).
|
|
124
|
+
*/
|
|
125
|
+
async set(key, data) {
|
|
126
|
+
await this.initialize();
|
|
127
|
+
try {
|
|
128
|
+
// Validate against schema if provided
|
|
129
|
+
if (this.schema) {
|
|
130
|
+
const validation = this.schema.validate(data);
|
|
131
|
+
if (!validation.valid) {
|
|
132
|
+
throw new Error(`Validation failed: ${validation.errors.join(", ")}`);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
const dataWithDefaults = this.schema
|
|
136
|
+
? this.schema.applyDefaults(data)
|
|
137
|
+
: data;
|
|
138
|
+
if (this.config.type === "mongodb") {
|
|
139
|
+
await this.mongoCollection.updateOne({ _id: key }, {
|
|
140
|
+
$set: {
|
|
141
|
+
...dataWithDefaults,
|
|
142
|
+
_id: key,
|
|
143
|
+
updatedAt: new Date(),
|
|
144
|
+
},
|
|
145
|
+
$setOnInsert: {
|
|
146
|
+
createdAt: new Date(),
|
|
147
|
+
},
|
|
148
|
+
}, { upsert: true });
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
const filePath = path.join(this.config.dataPath || "./data", `${key}.json`);
|
|
152
|
+
await fs.writeFile(filePath, JSON.stringify(dataWithDefaults, null, 2));
|
|
153
|
+
}
|
|
154
|
+
console.log(`✅ [MiniDatabase] Saved data for key "${key}"`);
|
|
155
|
+
return true;
|
|
156
|
+
}
|
|
157
|
+
catch (err) {
|
|
158
|
+
console.error(`❌ [MiniDatabase] Failed to set data for key "${key}":`, err);
|
|
159
|
+
return false;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Updates specific fields in data (merges with existing data).
|
|
164
|
+
*/
|
|
165
|
+
async update(key, updates) {
|
|
166
|
+
await this.initialize();
|
|
167
|
+
try {
|
|
168
|
+
const existing = await this.get(key);
|
|
169
|
+
const merged = { ...existing, ...updates };
|
|
170
|
+
// Validate merged data against schema if provided
|
|
171
|
+
if (this.schema) {
|
|
172
|
+
const validation = this.schema.validate(merged);
|
|
173
|
+
if (!validation.valid) {
|
|
174
|
+
throw new Error(`Validation failed: ${validation.errors.join(", ")}`);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
const dataWithDefaults = this.schema
|
|
178
|
+
? this.schema.applyDefaults(merged)
|
|
179
|
+
: merged;
|
|
180
|
+
if (this.config.type === "mongodb") {
|
|
181
|
+
await this.mongoCollection.updateOne({ _id: key }, {
|
|
182
|
+
$set: {
|
|
183
|
+
...dataWithDefaults,
|
|
184
|
+
updatedAt: new Date(),
|
|
185
|
+
},
|
|
186
|
+
$setOnInsert: {
|
|
187
|
+
createdAt: new Date(),
|
|
188
|
+
},
|
|
189
|
+
}, { upsert: true });
|
|
190
|
+
}
|
|
191
|
+
else {
|
|
192
|
+
const filePath = path.join(this.config.dataPath || "./data", `${key}.json`);
|
|
193
|
+
await fs.writeFile(filePath, JSON.stringify(dataWithDefaults, null, 2));
|
|
194
|
+
}
|
|
195
|
+
console.log(`✅ [MiniDatabase] Updated data for key "${key}"`);
|
|
196
|
+
return true;
|
|
197
|
+
}
|
|
198
|
+
catch (err) {
|
|
199
|
+
console.error(`❌ [MiniDatabase] Failed to update data for key "${key}":`, err);
|
|
200
|
+
return false;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Deletes data by key.
|
|
205
|
+
*/
|
|
206
|
+
async delete(key) {
|
|
207
|
+
await this.initialize();
|
|
208
|
+
try {
|
|
209
|
+
if (this.config.type === "mongodb") {
|
|
210
|
+
await this.mongoCollection.deleteOne({ _id: key });
|
|
211
|
+
}
|
|
212
|
+
else {
|
|
213
|
+
const filePath = path.join(this.config.dataPath || "./data", `${key}.json`);
|
|
214
|
+
await fs.unlink(filePath);
|
|
215
|
+
}
|
|
216
|
+
console.log(`✅ [MiniDatabase] Deleted data for key "${key}"`);
|
|
217
|
+
return true;
|
|
218
|
+
}
|
|
219
|
+
catch (err) {
|
|
220
|
+
console.error(`❌ [MiniDatabase] Failed to delete data for key "${key}":`, err);
|
|
221
|
+
return false;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Closes the database connection (for MongoDB).
|
|
226
|
+
*/
|
|
227
|
+
async close() {
|
|
228
|
+
if (this.mongoClient) {
|
|
229
|
+
await this.mongoClient.close();
|
|
230
|
+
console.log("✅ [MiniDatabase] MongoDB connection closed");
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration for MiniDatabase backend.
|
|
3
|
+
*/
|
|
4
|
+
export interface DatabaseConfig {
|
|
5
|
+
type: "json" | "mongodb";
|
|
6
|
+
dataPath?: string;
|
|
7
|
+
mongoUri?: string;
|
|
8
|
+
dbName?: string;
|
|
9
|
+
collectionName?: string;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Builder for configuring MiniDatabase.
|
|
13
|
+
* Provides a fluent API for easy database setup.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```typescript
|
|
17
|
+
* // Using JSON (default)
|
|
18
|
+
* const db = new MiniDatabaseBuilder()
|
|
19
|
+
* .setType("json")
|
|
20
|
+
* .setDataPath("./data")
|
|
21
|
+
* .build();
|
|
22
|
+
*
|
|
23
|
+
* // Using MongoDB
|
|
24
|
+
* const db = new MiniDatabaseBuilder()
|
|
25
|
+
* .setType("mongodb")
|
|
26
|
+
* .setMongoUri(process.env.MONGO_URI)
|
|
27
|
+
* .setDbName("myapp")
|
|
28
|
+
* .setCollectionName("users")
|
|
29
|
+
* .build();
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
export declare class MiniDatabaseBuilder {
|
|
33
|
+
private config;
|
|
34
|
+
/**
|
|
35
|
+
* Sets the database type (json or mongodb).
|
|
36
|
+
*/
|
|
37
|
+
setType(type: "json" | "mongodb"): this;
|
|
38
|
+
/**
|
|
39
|
+
* Sets the data path for JSON backend.
|
|
40
|
+
* Default: "./data"
|
|
41
|
+
*/
|
|
42
|
+
setDataPath(path: string): this;
|
|
43
|
+
/**
|
|
44
|
+
* Sets the MongoDB connection URI.
|
|
45
|
+
*/
|
|
46
|
+
setMongoUri(uri: string): this;
|
|
47
|
+
/**
|
|
48
|
+
* Sets the MongoDB database name.
|
|
49
|
+
* Default: "minidb"
|
|
50
|
+
*/
|
|
51
|
+
setDbName(name: string): this;
|
|
52
|
+
/**
|
|
53
|
+
* Sets the MongoDB collection name.
|
|
54
|
+
* Default: "data"
|
|
55
|
+
*/
|
|
56
|
+
setCollectionName(name: string): this;
|
|
57
|
+
/**
|
|
58
|
+
* Gets the current configuration.
|
|
59
|
+
*/
|
|
60
|
+
getConfig(): DatabaseConfig;
|
|
61
|
+
/**
|
|
62
|
+
* Validates the configuration.
|
|
63
|
+
*/
|
|
64
|
+
validate(): {
|
|
65
|
+
valid: boolean;
|
|
66
|
+
errors: string[];
|
|
67
|
+
};
|
|
68
|
+
/**
|
|
69
|
+
* Builds and returns the configuration.
|
|
70
|
+
*/
|
|
71
|
+
build(): DatabaseConfig;
|
|
72
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Builder for configuring MiniDatabase.
|
|
3
|
+
* Provides a fluent API for easy database setup.
|
|
4
|
+
*
|
|
5
|
+
* @example
|
|
6
|
+
* ```typescript
|
|
7
|
+
* // Using JSON (default)
|
|
8
|
+
* const db = new MiniDatabaseBuilder()
|
|
9
|
+
* .setType("json")
|
|
10
|
+
* .setDataPath("./data")
|
|
11
|
+
* .build();
|
|
12
|
+
*
|
|
13
|
+
* // Using MongoDB
|
|
14
|
+
* const db = new MiniDatabaseBuilder()
|
|
15
|
+
* .setType("mongodb")
|
|
16
|
+
* .setMongoUri(process.env.MONGO_URI)
|
|
17
|
+
* .setDbName("myapp")
|
|
18
|
+
* .setCollectionName("users")
|
|
19
|
+
* .build();
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
export class MiniDatabaseBuilder {
|
|
23
|
+
config = {
|
|
24
|
+
type: "json",
|
|
25
|
+
dataPath: "./data",
|
|
26
|
+
dbName: "minidb",
|
|
27
|
+
collectionName: "data",
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* Sets the database type (json or mongodb).
|
|
31
|
+
*/
|
|
32
|
+
setType(type) {
|
|
33
|
+
this.config.type = type;
|
|
34
|
+
return this;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Sets the data path for JSON backend.
|
|
38
|
+
* Default: "./data"
|
|
39
|
+
*/
|
|
40
|
+
setDataPath(path) {
|
|
41
|
+
this.config.dataPath = path;
|
|
42
|
+
return this;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Sets the MongoDB connection URI.
|
|
46
|
+
*/
|
|
47
|
+
setMongoUri(uri) {
|
|
48
|
+
this.config.mongoUri = uri;
|
|
49
|
+
return this;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Sets the MongoDB database name.
|
|
53
|
+
* Default: "minidb"
|
|
54
|
+
*/
|
|
55
|
+
setDbName(name) {
|
|
56
|
+
this.config.dbName = name;
|
|
57
|
+
return this;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Sets the MongoDB collection name.
|
|
61
|
+
* Default: "data"
|
|
62
|
+
*/
|
|
63
|
+
setCollectionName(name) {
|
|
64
|
+
this.config.collectionName = name;
|
|
65
|
+
return this;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Gets the current configuration.
|
|
69
|
+
*/
|
|
70
|
+
getConfig() {
|
|
71
|
+
return { ...this.config };
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Validates the configuration.
|
|
75
|
+
*/
|
|
76
|
+
validate() {
|
|
77
|
+
const errors = [];
|
|
78
|
+
if (this.config.type === "mongodb") {
|
|
79
|
+
if (!this.config.mongoUri) {
|
|
80
|
+
errors.push("MongoDB URI is required when using mongodb backend");
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
if (this.config.type === "json") {
|
|
84
|
+
if (!this.config.dataPath) {
|
|
85
|
+
errors.push("Data path is required when using json backend");
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return {
|
|
89
|
+
valid: errors.length === 0,
|
|
90
|
+
errors,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Builds and returns the configuration.
|
|
95
|
+
*/
|
|
96
|
+
build() {
|
|
97
|
+
const validation = this.validate();
|
|
98
|
+
if (!validation.valid) {
|
|
99
|
+
throw new Error(`Invalid database configuration: ${validation.errors.join(", ")}`);
|
|
100
|
+
}
|
|
101
|
+
return this.getConfig();
|
|
102
|
+
}
|
|
103
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -21,3 +21,11 @@ export { TextInputStyle } from "discord-api-types/v10";
|
|
|
21
21
|
export { MiniPermFlags } from "./types/PermissionFlags.js";
|
|
22
22
|
export type { MiniComponentActionRow, MiniComponentMessageActionRow, } from "./types/ComponentTypes.js";
|
|
23
23
|
export * from "./builders/index.js";
|
|
24
|
+
export { MiniDataBuilder } from "./database/MiniDataBuilder.js";
|
|
25
|
+
export type { DataField } from "./database/MiniDataBuilder.js";
|
|
26
|
+
export { MiniDatabaseBuilder } from "./database/MiniDatabaseBuilder.js";
|
|
27
|
+
export type { DatabaseConfig } from "./database/MiniDatabaseBuilder.js";
|
|
28
|
+
export { MiniDatabase } from "./database/MiniDatabase.js";
|
|
29
|
+
export { generateOAuthUrl, getOAuthTokens, refreshAccessToken, getDiscordUser, ensureValidToken, } from "./oauth/DiscordOAuth.js";
|
|
30
|
+
export type { OAuthConfig, OAuthTokens, DiscordUser, } from "./oauth/DiscordOAuth.js";
|
|
31
|
+
export { OAuthTokenStorage } from "./oauth/OAuthTokenStorage.js";
|
package/dist/index.js
CHANGED
|
@@ -11,3 +11,8 @@ export { SeparatorSpacingSize } from "./types/SeparatorSpacingSize.js";
|
|
|
11
11
|
export { TextInputStyle } from "discord-api-types/v10";
|
|
12
12
|
export { MiniPermFlags } from "./types/PermissionFlags.js";
|
|
13
13
|
export * from "./builders/index.js";
|
|
14
|
+
export { MiniDataBuilder } from "./database/MiniDataBuilder.js";
|
|
15
|
+
export { MiniDatabaseBuilder } from "./database/MiniDatabaseBuilder.js";
|
|
16
|
+
export { MiniDatabase } from "./database/MiniDatabase.js";
|
|
17
|
+
export { generateOAuthUrl, getOAuthTokens, refreshAccessToken, getDiscordUser, ensureValidToken, } from "./oauth/DiscordOAuth.js";
|
|
18
|
+
export { OAuthTokenStorage } from "./oauth/OAuthTokenStorage.js";
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Discord OAuth utilities for handling the OAuth2 flow.
|
|
3
|
+
* Simplifies Discord authentication for mini-interaction apps.
|
|
4
|
+
*/
|
|
5
|
+
export interface OAuthConfig {
|
|
6
|
+
appId: string;
|
|
7
|
+
appSecret: string;
|
|
8
|
+
redirectUri: string;
|
|
9
|
+
}
|
|
10
|
+
export interface OAuthTokens {
|
|
11
|
+
access_token: string;
|
|
12
|
+
refresh_token: string;
|
|
13
|
+
expires_at: number;
|
|
14
|
+
token_type: string;
|
|
15
|
+
scope: string;
|
|
16
|
+
}
|
|
17
|
+
export interface DiscordUser {
|
|
18
|
+
id: string;
|
|
19
|
+
username: string;
|
|
20
|
+
discriminator: string;
|
|
21
|
+
avatar: string | null;
|
|
22
|
+
email?: string;
|
|
23
|
+
verified?: boolean;
|
|
24
|
+
locale?: string;
|
|
25
|
+
mfa_enabled?: boolean;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Generates an OAuth authorization URL for Discord.
|
|
29
|
+
*/
|
|
30
|
+
export declare function generateOAuthUrl(config: OAuthConfig, scopes?: string[]): {
|
|
31
|
+
url: string;
|
|
32
|
+
state: string;
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* Exchanges an OAuth code for access tokens.
|
|
36
|
+
*/
|
|
37
|
+
export declare function getOAuthTokens(code: string, config: OAuthConfig): Promise<OAuthTokens>;
|
|
38
|
+
/**
|
|
39
|
+
* Refreshes an expired access token.
|
|
40
|
+
*/
|
|
41
|
+
export declare function refreshAccessToken(refreshToken: string, config: OAuthConfig): Promise<OAuthTokens>;
|
|
42
|
+
/**
|
|
43
|
+
* Gets the current user's Discord profile.
|
|
44
|
+
*/
|
|
45
|
+
export declare function getDiscordUser(accessToken: string): Promise<DiscordUser>;
|
|
46
|
+
/**
|
|
47
|
+
* Ensures the access token is valid, refreshing if necessary.
|
|
48
|
+
*/
|
|
49
|
+
export declare function ensureValidToken(tokens: OAuthTokens, config: OAuthConfig): Promise<OAuthTokens>;
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import crypto from "crypto";
|
|
2
|
+
/**
|
|
3
|
+
* Generates an OAuth authorization URL for Discord.
|
|
4
|
+
*/
|
|
5
|
+
export function generateOAuthUrl(config, scopes = ["identify", "email"]) {
|
|
6
|
+
const state = crypto.randomUUID();
|
|
7
|
+
const url = new URL("https://discord.com/api/oauth2/authorize");
|
|
8
|
+
url.searchParams.set("client_id", config.appId);
|
|
9
|
+
url.searchParams.set("redirect_uri", config.redirectUri);
|
|
10
|
+
url.searchParams.set("response_type", "code");
|
|
11
|
+
url.searchParams.set("state", state);
|
|
12
|
+
url.searchParams.set("scope", scopes.join(" "));
|
|
13
|
+
url.searchParams.set("prompt", "consent");
|
|
14
|
+
return { url: url.toString(), state };
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Exchanges an OAuth code for access tokens.
|
|
18
|
+
*/
|
|
19
|
+
export async function getOAuthTokens(code, config) {
|
|
20
|
+
const url = "https://discord.com/api/v10/oauth2/token";
|
|
21
|
+
const body = new URLSearchParams({
|
|
22
|
+
client_id: config.appId,
|
|
23
|
+
client_secret: config.appSecret,
|
|
24
|
+
grant_type: "authorization_code",
|
|
25
|
+
code,
|
|
26
|
+
redirect_uri: config.redirectUri,
|
|
27
|
+
});
|
|
28
|
+
const response = await fetch(url, {
|
|
29
|
+
method: "POST",
|
|
30
|
+
body,
|
|
31
|
+
headers: {
|
|
32
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
33
|
+
},
|
|
34
|
+
});
|
|
35
|
+
if (!response.ok) {
|
|
36
|
+
throw new Error(`Failed to get OAuth tokens: [${response.status}] ${response.statusText}`);
|
|
37
|
+
}
|
|
38
|
+
const data = (await response.json());
|
|
39
|
+
return {
|
|
40
|
+
access_token: data.access_token,
|
|
41
|
+
refresh_token: data.refresh_token,
|
|
42
|
+
expires_at: Date.now() + data.expires_in * 1000,
|
|
43
|
+
token_type: data.token_type,
|
|
44
|
+
scope: data.scope,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Refreshes an expired access token.
|
|
49
|
+
*/
|
|
50
|
+
export async function refreshAccessToken(refreshToken, config) {
|
|
51
|
+
const url = "https://discord.com/api/v10/oauth2/token";
|
|
52
|
+
const body = new URLSearchParams({
|
|
53
|
+
client_id: config.appId,
|
|
54
|
+
client_secret: config.appSecret,
|
|
55
|
+
grant_type: "refresh_token",
|
|
56
|
+
refresh_token: refreshToken,
|
|
57
|
+
});
|
|
58
|
+
const response = await fetch(url, {
|
|
59
|
+
method: "POST",
|
|
60
|
+
body,
|
|
61
|
+
headers: {
|
|
62
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
if (!response.ok) {
|
|
66
|
+
throw new Error(`Failed to refresh access token: [${response.status}] ${response.statusText}`);
|
|
67
|
+
}
|
|
68
|
+
const data = (await response.json());
|
|
69
|
+
return {
|
|
70
|
+
access_token: data.access_token,
|
|
71
|
+
refresh_token: data.refresh_token,
|
|
72
|
+
expires_at: Date.now() + data.expires_in * 1000,
|
|
73
|
+
token_type: data.token_type,
|
|
74
|
+
scope: data.scope,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Gets the current user's Discord profile.
|
|
79
|
+
*/
|
|
80
|
+
export async function getDiscordUser(accessToken) {
|
|
81
|
+
const url = "https://discord.com/api/v10/oauth2/@me";
|
|
82
|
+
const response = await fetch(url, {
|
|
83
|
+
headers: {
|
|
84
|
+
Authorization: `Bearer ${accessToken}`,
|
|
85
|
+
},
|
|
86
|
+
});
|
|
87
|
+
if (!response.ok) {
|
|
88
|
+
throw new Error(`Failed to get Discord user: [${response.status}] ${response.statusText}`);
|
|
89
|
+
}
|
|
90
|
+
const data = (await response.json());
|
|
91
|
+
return {
|
|
92
|
+
id: data.user.id,
|
|
93
|
+
username: data.user.username,
|
|
94
|
+
discriminator: data.user.discriminator,
|
|
95
|
+
avatar: data.user.avatar,
|
|
96
|
+
email: data.user.email,
|
|
97
|
+
verified: data.user.verified,
|
|
98
|
+
locale: data.user.locale,
|
|
99
|
+
mfa_enabled: data.user.mfa_enabled,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Ensures the access token is valid, refreshing if necessary.
|
|
104
|
+
*/
|
|
105
|
+
export async function ensureValidToken(tokens, config) {
|
|
106
|
+
if (Date.now() > tokens.expires_at) {
|
|
107
|
+
return refreshAccessToken(tokens.refresh_token, config);
|
|
108
|
+
}
|
|
109
|
+
return tokens;
|
|
110
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { MiniDatabase } from "../database/MiniDatabase.js";
|
|
2
|
+
import type { OAuthTokens } from "./DiscordOAuth.js";
|
|
3
|
+
/**
|
|
4
|
+
* Manages OAuth token storage using MiniDatabase.
|
|
5
|
+
* Handles both in-memory and persistent storage.
|
|
6
|
+
*/
|
|
7
|
+
export declare class OAuthTokenStorage {
|
|
8
|
+
private db?;
|
|
9
|
+
private inMemoryStore;
|
|
10
|
+
constructor(db?: MiniDatabase);
|
|
11
|
+
/**
|
|
12
|
+
* Stores OAuth tokens for a user.
|
|
13
|
+
*/
|
|
14
|
+
storeTokens(userId: string, tokens: OAuthTokens): Promise<boolean>;
|
|
15
|
+
/**
|
|
16
|
+
* Retrieves OAuth tokens for a user.
|
|
17
|
+
*/
|
|
18
|
+
getTokens(userId: string): Promise<OAuthTokens | null>;
|
|
19
|
+
/**
|
|
20
|
+
* Updates OAuth tokens for a user.
|
|
21
|
+
*/
|
|
22
|
+
updateTokens(userId: string, tokens: OAuthTokens): Promise<boolean>;
|
|
23
|
+
/**
|
|
24
|
+
* Deletes OAuth tokens for a user.
|
|
25
|
+
*/
|
|
26
|
+
deleteTokens(userId: string): Promise<boolean>;
|
|
27
|
+
/**
|
|
28
|
+
* Clears all in-memory tokens (useful for testing).
|
|
29
|
+
*/
|
|
30
|
+
clearMemory(): void;
|
|
31
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Manages OAuth token storage using MiniDatabase.
|
|
3
|
+
* Handles both in-memory and persistent storage.
|
|
4
|
+
*/
|
|
5
|
+
export class OAuthTokenStorage {
|
|
6
|
+
db;
|
|
7
|
+
inMemoryStore = new Map();
|
|
8
|
+
constructor(db) {
|
|
9
|
+
this.db = db;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Stores OAuth tokens for a user.
|
|
13
|
+
*/
|
|
14
|
+
async storeTokens(userId, tokens) {
|
|
15
|
+
try {
|
|
16
|
+
// Always store in memory for fast access
|
|
17
|
+
this.inMemoryStore.set(`oauth-${userId}`, tokens);
|
|
18
|
+
// Also store in database if available
|
|
19
|
+
if (this.db) {
|
|
20
|
+
return await this.db.set(`oauth-${userId}`, {
|
|
21
|
+
userId,
|
|
22
|
+
...tokens,
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
catch (err) {
|
|
28
|
+
console.error(`❌ [OAuthTokenStorage] Failed to store tokens for ${userId}:`, err);
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Retrieves OAuth tokens for a user.
|
|
34
|
+
*/
|
|
35
|
+
async getTokens(userId) {
|
|
36
|
+
try {
|
|
37
|
+
// Check in-memory store first
|
|
38
|
+
const cached = this.inMemoryStore.get(`oauth-${userId}`);
|
|
39
|
+
if (cached) {
|
|
40
|
+
return cached;
|
|
41
|
+
}
|
|
42
|
+
// Check database if available
|
|
43
|
+
if (this.db) {
|
|
44
|
+
const data = await this.db.get(`oauth-${userId}`);
|
|
45
|
+
if (data) {
|
|
46
|
+
const tokens = {
|
|
47
|
+
access_token: data.access_token,
|
|
48
|
+
refresh_token: data.refresh_token,
|
|
49
|
+
expires_at: data.expires_at,
|
|
50
|
+
token_type: data.token_type,
|
|
51
|
+
scope: data.scope,
|
|
52
|
+
};
|
|
53
|
+
// Cache in memory
|
|
54
|
+
this.inMemoryStore.set(`oauth-${userId}`, tokens);
|
|
55
|
+
return tokens;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
catch (err) {
|
|
61
|
+
console.error(`❌ [OAuthTokenStorage] Failed to get tokens for ${userId}:`, err);
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Updates OAuth tokens for a user.
|
|
67
|
+
*/
|
|
68
|
+
async updateTokens(userId, tokens) {
|
|
69
|
+
try {
|
|
70
|
+
// Update in-memory store
|
|
71
|
+
this.inMemoryStore.set(`oauth-${userId}`, tokens);
|
|
72
|
+
// Update in database if available
|
|
73
|
+
if (this.db) {
|
|
74
|
+
return await this.db.update(`oauth-${userId}`, {
|
|
75
|
+
...tokens,
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
return true;
|
|
79
|
+
}
|
|
80
|
+
catch (err) {
|
|
81
|
+
console.error(`❌ [OAuthTokenStorage] Failed to update tokens for ${userId}:`, err);
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Deletes OAuth tokens for a user.
|
|
87
|
+
*/
|
|
88
|
+
async deleteTokens(userId) {
|
|
89
|
+
try {
|
|
90
|
+
// Remove from in-memory store
|
|
91
|
+
this.inMemoryStore.delete(`oauth-${userId}`);
|
|
92
|
+
// Remove from database if available
|
|
93
|
+
if (this.db) {
|
|
94
|
+
return await this.db.delete(`oauth-${userId}`);
|
|
95
|
+
}
|
|
96
|
+
return true;
|
|
97
|
+
}
|
|
98
|
+
catch (err) {
|
|
99
|
+
console.error(`❌ [OAuthTokenStorage] Failed to delete tokens for ${userId}:`, err);
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Clears all in-memory tokens (useful for testing).
|
|
105
|
+
*/
|
|
106
|
+
clearMemory() {
|
|
107
|
+
this.inMemoryStore.clear();
|
|
108
|
+
}
|
|
109
|
+
}
|
package/package.json
CHANGED