@iskra-bun/web-kit 0.1.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 +7 -0
- package/README.md +31 -0
- package/dist/chunk-POXNRNTC.js +51 -0
- package/dist/chunk-POXNRNTC.js.map +1 -0
- package/dist/index.d.ts +966 -0
- package/dist/index.js +2824 -0
- package/dist/index.js.map +1 -0
- package/dist/mailgun-Z46GZJNI.js +83 -0
- package/dist/mailgun-Z46GZJNI.js.map +1 -0
- package/dist/s3-7IG4ESFW.js +171 -0
- package/dist/s3-7IG4ESFW.js.map +1 -0
- package/dist/sendgrid-UK2GSBEF.js +43 -0
- package/dist/sendgrid-UK2GSBEF.js.map +1 -0
- package/dist/smtp-WJDLYKD5.js +50 -0
- package/dist/smtp-WJDLYKD5.js.map +1 -0
- package/package.json +74 -0
- package/src/driver.ts +55 -0
- package/src/errors.ts +66 -0
- package/src/features/api-key.ts +243 -0
- package/src/features/auth/better-auth-config.ts +160 -0
- package/src/features/auth/index.ts +229 -0
- package/src/features/auth/schema.ts +174 -0
- package/src/features/auth/types.ts +114 -0
- package/src/features/cache.ts +144 -0
- package/src/features/cors.ts +33 -0
- package/src/features/csrf.ts +94 -0
- package/src/features/db.ts +90 -0
- package/src/features/email/index.ts +103 -0
- package/src/features/email/providers/mailgun.ts +99 -0
- package/src/features/email/providers/sendgrid.ts +42 -0
- package/src/features/email/providers/smtp.ts +51 -0
- package/src/features/error-handler.ts +147 -0
- package/src/features/health.ts +94 -0
- package/src/features/json-schema-validation.ts +186 -0
- package/src/features/logger.ts +70 -0
- package/src/features/openapi.ts +107 -0
- package/src/features/permissions.ts +128 -0
- package/src/features/rate-limit.ts +173 -0
- package/src/features/request-id.ts +45 -0
- package/src/features/session.ts +322 -0
- package/src/features/storage/adapters/local.ts +133 -0
- package/src/features/storage/adapters/s3.ts +193 -0
- package/src/features/storage/base.ts +112 -0
- package/src/features/storage/index.ts +53 -0
- package/src/features/tracing.ts +49 -0
- package/src/features/upload/helper.ts +85 -0
- package/src/features/upload/index.ts +140 -0
- package/src/features/validation.ts +105 -0
- package/src/index.ts +29 -0
- package/src/kernel.ts +257 -0
- package/src/responses.ts +37 -0
- package/src/router.ts +31 -0
- package/src/server.ts +135 -0
- package/src/types.ts +272 -0
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import {
|
|
2
|
+
BaseStorageAdapter
|
|
3
|
+
} from "./chunk-POXNRNTC.js";
|
|
4
|
+
|
|
5
|
+
// src/features/storage/adapters/s3.ts
|
|
6
|
+
import {
|
|
7
|
+
S3Client,
|
|
8
|
+
PutObjectCommand,
|
|
9
|
+
GetObjectCommand,
|
|
10
|
+
DeleteObjectCommand,
|
|
11
|
+
HeadObjectCommand,
|
|
12
|
+
HeadBucketCommand,
|
|
13
|
+
ListObjectsV2Command,
|
|
14
|
+
CopyObjectCommand
|
|
15
|
+
} from "@aws-sdk/client-s3";
|
|
16
|
+
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
|
|
17
|
+
var S3StorageAdapter = class extends BaseStorageAdapter {
|
|
18
|
+
constructor(config) {
|
|
19
|
+
super();
|
|
20
|
+
this.config = config;
|
|
21
|
+
const conn = config.connection || {};
|
|
22
|
+
this.bucket = conn.bucket || "iskra-storage";
|
|
23
|
+
this.client = new S3Client({
|
|
24
|
+
endpoint: conn.endpoint,
|
|
25
|
+
region: conn.region || "us-east-1",
|
|
26
|
+
credentials: conn.accessKey && conn.secretKey ? { accessKeyId: conn.accessKey, secretAccessKey: conn.secretKey } : void 0,
|
|
27
|
+
forcePathStyle: !!conn.endpoint
|
|
28
|
+
// true for MinIO
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
config;
|
|
32
|
+
client;
|
|
33
|
+
bucket;
|
|
34
|
+
async connect() {
|
|
35
|
+
try {
|
|
36
|
+
await this.client.send(new HeadBucketCommand({ Bucket: this.bucket }));
|
|
37
|
+
this.connected = true;
|
|
38
|
+
} catch (err) {
|
|
39
|
+
throw new Error(`Failed to connect to S3 bucket "${this.bucket}": ${err.message}`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
async disconnect() {
|
|
43
|
+
this.client.destroy();
|
|
44
|
+
this.connected = false;
|
|
45
|
+
}
|
|
46
|
+
async put(path, data, options) {
|
|
47
|
+
this.ensureConnected();
|
|
48
|
+
const key = this.sanitizePath(path);
|
|
49
|
+
let body;
|
|
50
|
+
if (data instanceof ReadableStream) {
|
|
51
|
+
const response = new Response(data);
|
|
52
|
+
body = new Uint8Array(await response.arrayBuffer());
|
|
53
|
+
} else {
|
|
54
|
+
body = data;
|
|
55
|
+
}
|
|
56
|
+
await this.client.send(new PutObjectCommand({
|
|
57
|
+
Bucket: this.bucket,
|
|
58
|
+
Key: key,
|
|
59
|
+
Body: body,
|
|
60
|
+
ContentType: options?.contentType || this.getMimeType(key),
|
|
61
|
+
Metadata: options?.metadata
|
|
62
|
+
}));
|
|
63
|
+
return {
|
|
64
|
+
name: key.split("/").pop() || key,
|
|
65
|
+
path: key,
|
|
66
|
+
size: body.length,
|
|
67
|
+
mimeType: options?.contentType || this.getMimeType(key),
|
|
68
|
+
lastModified: /* @__PURE__ */ new Date()
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
async get(path) {
|
|
72
|
+
this.ensureConnected();
|
|
73
|
+
const key = this.sanitizePath(path);
|
|
74
|
+
try {
|
|
75
|
+
const response = await this.client.send(new GetObjectCommand({
|
|
76
|
+
Bucket: this.bucket,
|
|
77
|
+
Key: key
|
|
78
|
+
}));
|
|
79
|
+
if (!response.Body) return null;
|
|
80
|
+
return new Uint8Array(await response.Body.transformToByteArray());
|
|
81
|
+
} catch (err) {
|
|
82
|
+
if (err.name === "NoSuchKey" || err.$metadata?.httpStatusCode === 404) {
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
throw err;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
async delete(path) {
|
|
89
|
+
this.ensureConnected();
|
|
90
|
+
const key = this.sanitizePath(path);
|
|
91
|
+
await this.client.send(new DeleteObjectCommand({
|
|
92
|
+
Bucket: this.bucket,
|
|
93
|
+
Key: key
|
|
94
|
+
}));
|
|
95
|
+
}
|
|
96
|
+
async exists(path) {
|
|
97
|
+
this.ensureConnected();
|
|
98
|
+
const key = this.sanitizePath(path);
|
|
99
|
+
try {
|
|
100
|
+
await this.client.send(new HeadObjectCommand({
|
|
101
|
+
Bucket: this.bucket,
|
|
102
|
+
Key: key
|
|
103
|
+
}));
|
|
104
|
+
return true;
|
|
105
|
+
} catch (err) {
|
|
106
|
+
if (err.name === "NotFound" || err.$metadata?.httpStatusCode === 404) {
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
throw err;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
async list(prefix) {
|
|
113
|
+
this.ensureConnected();
|
|
114
|
+
const files = [];
|
|
115
|
+
let continuationToken;
|
|
116
|
+
do {
|
|
117
|
+
const response = await this.client.send(new ListObjectsV2Command({
|
|
118
|
+
Bucket: this.bucket,
|
|
119
|
+
Prefix: prefix ? this.sanitizePath(prefix) : void 0,
|
|
120
|
+
ContinuationToken: continuationToken
|
|
121
|
+
}));
|
|
122
|
+
if (response.Contents) {
|
|
123
|
+
for (const obj of response.Contents) {
|
|
124
|
+
if (!obj.Key) continue;
|
|
125
|
+
files.push({
|
|
126
|
+
name: obj.Key.split("/").pop() || obj.Key,
|
|
127
|
+
path: obj.Key,
|
|
128
|
+
size: obj.Size || 0,
|
|
129
|
+
mimeType: this.getMimeType(obj.Key),
|
|
130
|
+
lastModified: obj.LastModified
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
continuationToken = response.IsTruncated ? response.NextContinuationToken : void 0;
|
|
135
|
+
} while (continuationToken);
|
|
136
|
+
return files;
|
|
137
|
+
}
|
|
138
|
+
async url(path, expiresIn = 3600) {
|
|
139
|
+
this.ensureConnected();
|
|
140
|
+
const key = this.sanitizePath(path);
|
|
141
|
+
const command = new GetObjectCommand({
|
|
142
|
+
Bucket: this.bucket,
|
|
143
|
+
Key: key
|
|
144
|
+
});
|
|
145
|
+
return await getSignedUrl(this.client, command, { expiresIn });
|
|
146
|
+
}
|
|
147
|
+
async copy(from, to) {
|
|
148
|
+
this.ensureConnected();
|
|
149
|
+
const sourceKey = this.sanitizePath(from);
|
|
150
|
+
const destKey = this.sanitizePath(to);
|
|
151
|
+
await this.client.send(new CopyObjectCommand({
|
|
152
|
+
Bucket: this.bucket,
|
|
153
|
+
CopySource: `${this.bucket}/${sourceKey}`,
|
|
154
|
+
Key: destKey
|
|
155
|
+
}));
|
|
156
|
+
}
|
|
157
|
+
async isDirectory(path) {
|
|
158
|
+
this.ensureConnected();
|
|
159
|
+
const prefix = this.sanitizePath(path).replace(/\/?$/, "/");
|
|
160
|
+
const response = await this.client.send(new ListObjectsV2Command({
|
|
161
|
+
Bucket: this.bucket,
|
|
162
|
+
Prefix: prefix,
|
|
163
|
+
MaxKeys: 1
|
|
164
|
+
}));
|
|
165
|
+
return (response.Contents?.length || 0) > 0;
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
export {
|
|
169
|
+
S3StorageAdapter
|
|
170
|
+
};
|
|
171
|
+
//# sourceMappingURL=s3-7IG4ESFW.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/features/storage/adapters/s3.ts"],"sourcesContent":["import { BaseStorageAdapter } from \"../base\";\nimport type { StorageConfig, StorageFile, PutOptions } from \"../base\";\nimport {\n S3Client,\n PutObjectCommand,\n GetObjectCommand,\n DeleteObjectCommand,\n HeadObjectCommand,\n HeadBucketCommand,\n ListObjectsV2Command,\n CopyObjectCommand,\n} from \"@aws-sdk/client-s3\";\nimport { getSignedUrl } from \"@aws-sdk/s3-request-presigner\";\n\nexport class S3StorageAdapter extends BaseStorageAdapter {\n private client: S3Client;\n private bucket: string;\n\n constructor(private config: StorageConfig) {\n super();\n const conn = config.connection || {};\n\n this.bucket = conn.bucket || \"iskra-storage\";\n\n this.client = new S3Client({\n endpoint: conn.endpoint,\n region: conn.region || \"us-east-1\",\n credentials: conn.accessKey && conn.secretKey\n ? { accessKeyId: conn.accessKey, secretAccessKey: conn.secretKey }\n : undefined,\n forcePathStyle: !!conn.endpoint, // true for MinIO\n });\n }\n\n async connect(): Promise<void> {\n try {\n await this.client.send(new HeadBucketCommand({ Bucket: this.bucket }));\n this.connected = true;\n } catch (err: any) {\n throw new Error(`Failed to connect to S3 bucket \"${this.bucket}\": ${err.message}`);\n }\n }\n\n async disconnect(): Promise<void> {\n this.client.destroy();\n this.connected = false;\n }\n\n async put(path: string, data: Uint8Array | Buffer | ReadableStream, options?: PutOptions): Promise<StorageFile> {\n this.ensureConnected();\n const key = this.sanitizePath(path);\n\n let body: Uint8Array | Buffer;\n if (data instanceof ReadableStream) {\n const response = new Response(data);\n body = new Uint8Array(await response.arrayBuffer());\n } else {\n body = data;\n }\n\n await this.client.send(new PutObjectCommand({\n Bucket: this.bucket,\n Key: key,\n Body: body,\n ContentType: options?.contentType || this.getMimeType(key),\n Metadata: options?.metadata,\n }));\n\n return {\n name: key.split(\"/\").pop() || key,\n path: key,\n size: body.length,\n mimeType: options?.contentType || this.getMimeType(key),\n lastModified: new Date(),\n };\n }\n\n async get(path: string): Promise<Uint8Array | null> {\n this.ensureConnected();\n const key = this.sanitizePath(path);\n\n try {\n const response = await this.client.send(new GetObjectCommand({\n Bucket: this.bucket,\n Key: key,\n }));\n\n if (!response.Body) return null;\n return new Uint8Array(await response.Body.transformToByteArray());\n } catch (err: any) {\n if (err.name === \"NoSuchKey\" || err.$metadata?.httpStatusCode === 404) {\n return null;\n }\n throw err;\n }\n }\n\n async delete(path: string): Promise<void> {\n this.ensureConnected();\n const key = this.sanitizePath(path);\n\n await this.client.send(new DeleteObjectCommand({\n Bucket: this.bucket,\n Key: key,\n }));\n }\n\n async exists(path: string): Promise<boolean> {\n this.ensureConnected();\n const key = this.sanitizePath(path);\n\n try {\n await this.client.send(new HeadObjectCommand({\n Bucket: this.bucket,\n Key: key,\n }));\n return true;\n } catch (err: any) {\n if (err.name === \"NotFound\" || err.$metadata?.httpStatusCode === 404) {\n return false;\n }\n throw err;\n }\n }\n\n async list(prefix?: string): Promise<StorageFile[]> {\n this.ensureConnected();\n const files: StorageFile[] = [];\n let continuationToken: string | undefined;\n\n do {\n const response = await this.client.send(new ListObjectsV2Command({\n Bucket: this.bucket,\n Prefix: prefix ? this.sanitizePath(prefix) : undefined,\n ContinuationToken: continuationToken,\n }));\n\n if (response.Contents) {\n for (const obj of response.Contents) {\n if (!obj.Key) continue;\n files.push({\n name: obj.Key.split(\"/\").pop() || obj.Key,\n path: obj.Key,\n size: obj.Size || 0,\n mimeType: this.getMimeType(obj.Key),\n lastModified: obj.LastModified,\n });\n }\n }\n\n continuationToken = response.IsTruncated ? response.NextContinuationToken : undefined;\n } while (continuationToken);\n\n return files;\n }\n\n async url(path: string, expiresIn: number = 3600): Promise<string> {\n this.ensureConnected();\n const key = this.sanitizePath(path);\n\n const command = new GetObjectCommand({\n Bucket: this.bucket,\n Key: key,\n });\n\n return await getSignedUrl(this.client, command, { expiresIn });\n }\n\n async copy(from: string, to: string): Promise<void> {\n this.ensureConnected();\n const sourceKey = this.sanitizePath(from);\n const destKey = this.sanitizePath(to);\n\n await this.client.send(new CopyObjectCommand({\n Bucket: this.bucket,\n CopySource: `${this.bucket}/${sourceKey}`,\n Key: destKey,\n }));\n }\n\n async isDirectory(path: string): Promise<boolean> {\n this.ensureConnected();\n const prefix = this.sanitizePath(path).replace(/\\/?$/, \"/\");\n\n const response = await this.client.send(new ListObjectsV2Command({\n Bucket: this.bucket,\n Prefix: prefix,\n MaxKeys: 1,\n }));\n\n return (response.Contents?.length || 0) > 0;\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// src/features/email/providers/sendgrid.ts
|
|
2
|
+
import sgMail from "@sendgrid/mail";
|
|
3
|
+
var SendGridEmailAdapter = class {
|
|
4
|
+
constructor(config) {
|
|
5
|
+
this.config = config;
|
|
6
|
+
if (!config.apiKey) throw new Error("SendGrid API Key required");
|
|
7
|
+
sgMail.setApiKey(config.apiKey);
|
|
8
|
+
}
|
|
9
|
+
config;
|
|
10
|
+
async send(message) {
|
|
11
|
+
const from = message.from || this.config.from;
|
|
12
|
+
if (!from) throw new Error("From address required");
|
|
13
|
+
const msg = {
|
|
14
|
+
to: message.to,
|
|
15
|
+
from: from.name ? { email: from.email, name: from.name } : from.email,
|
|
16
|
+
subject: message.subject,
|
|
17
|
+
text: message.text,
|
|
18
|
+
html: message.html,
|
|
19
|
+
cc: message.cc,
|
|
20
|
+
bcc: message.bcc,
|
|
21
|
+
replyTo: message.replyTo,
|
|
22
|
+
attachments: message.attachments?.map((a) => ({
|
|
23
|
+
filename: a.filename,
|
|
24
|
+
content: typeof a.content === "string" ? a.content : Buffer.from(a.content).toString("base64"),
|
|
25
|
+
type: a.contentType,
|
|
26
|
+
disposition: "attachment"
|
|
27
|
+
}))
|
|
28
|
+
};
|
|
29
|
+
const [response] = await sgMail.send(msg);
|
|
30
|
+
return { messageId: response.headers["x-message-id"], success: true };
|
|
31
|
+
}
|
|
32
|
+
async sendTemplate(templateName, to, data) {
|
|
33
|
+
return this.send({
|
|
34
|
+
to,
|
|
35
|
+
subject: `Template: ${templateName}`,
|
|
36
|
+
html: `<p>Template ${templateName} rendered with ${JSON.stringify(data)}</p>`
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
export {
|
|
41
|
+
SendGridEmailAdapter
|
|
42
|
+
};
|
|
43
|
+
//# sourceMappingURL=sendgrid-UK2GSBEF.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/features/email/providers/sendgrid.ts"],"sourcesContent":["import type { EmailAdapter, EmailConfig, EmailMessage, TemplateData } from \"../index\";\nimport sgMail from \"@sendgrid/mail\";\n\nexport class SendGridEmailAdapter implements EmailAdapter {\n constructor(private config: EmailConfig) {\n if (!config.apiKey) throw new Error(\"SendGrid API Key required\");\n sgMail.setApiKey(config.apiKey);\n }\n\n async send(message: EmailMessage) {\n const from = message.from || this.config.from;\n if (!from) throw new Error(\"From address required\");\n\n const msg = {\n to: message.to,\n from: from.name ? { email: from.email, name: from.name } : from.email,\n subject: message.subject,\n text: message.text,\n html: message.html,\n cc: message.cc as any,\n bcc: message.bcc as any,\n replyTo: message.replyTo,\n attachments: message.attachments?.map(a => ({\n filename: a.filename,\n content: typeof a.content === 'string' ? a.content : Buffer.from(a.content).toString(\"base64\"),\n type: a.contentType,\n disposition: \"attachment\"\n }))\n } as any;\n\n const [response] = await sgMail.send(msg);\n return { messageId: response.headers[\"x-message-id\"] as string, success: true };\n }\n\n async sendTemplate(templateName: string, to: string | string[], data: TemplateData) {\n return this.send({\n to,\n subject: `Template: ${templateName}`,\n html: `<p>Template ${templateName} rendered with ${JSON.stringify(data)}</p>`\n });\n }\n}\n"],"mappings":";AACA,OAAO,YAAY;AAEZ,IAAM,uBAAN,MAAmD;AAAA,EACtD,YAAoB,QAAqB;AAArB;AAChB,QAAI,CAAC,OAAO,OAAQ,OAAM,IAAI,MAAM,2BAA2B;AAC/D,WAAO,UAAU,OAAO,MAAM;AAAA,EAClC;AAAA,EAHoB;AAAA,EAKpB,MAAM,KAAK,SAAuB;AAC9B,UAAM,OAAO,QAAQ,QAAQ,KAAK,OAAO;AACzC,QAAI,CAAC,KAAM,OAAM,IAAI,MAAM,uBAAuB;AAElD,UAAM,MAAM;AAAA,MACR,IAAI,QAAQ;AAAA,MACZ,MAAM,KAAK,OAAO,EAAE,OAAO,KAAK,OAAO,MAAM,KAAK,KAAK,IAAI,KAAK;AAAA,MAChE,SAAS,QAAQ;AAAA,MACjB,MAAM,QAAQ;AAAA,MACd,MAAM,QAAQ;AAAA,MACd,IAAI,QAAQ;AAAA,MACZ,KAAK,QAAQ;AAAA,MACb,SAAS,QAAQ;AAAA,MACjB,aAAa,QAAQ,aAAa,IAAI,QAAM;AAAA,QACxC,UAAU,EAAE;AAAA,QACZ,SAAS,OAAO,EAAE,YAAY,WAAW,EAAE,UAAU,OAAO,KAAK,EAAE,OAAO,EAAE,SAAS,QAAQ;AAAA,QAC7F,MAAM,EAAE;AAAA,QACR,aAAa;AAAA,MACjB,EAAE;AAAA,IACN;AAEA,UAAM,CAAC,QAAQ,IAAI,MAAM,OAAO,KAAK,GAAG;AACxC,WAAO,EAAE,WAAW,SAAS,QAAQ,cAAc,GAAa,SAAS,KAAK;AAAA,EAClF;AAAA,EAEA,MAAM,aAAa,cAAsB,IAAuB,MAAoB;AAChF,WAAO,KAAK,KAAK;AAAA,MACb;AAAA,MACA,SAAS,aAAa,YAAY;AAAA,MAClC,MAAM,eAAe,YAAY,kBAAkB,KAAK,UAAU,IAAI,CAAC;AAAA,IAC3E,CAAC;AAAA,EACL;AACJ;","names":[]}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// src/features/email/providers/smtp.ts
|
|
2
|
+
import * as nodemailer from "nodemailer";
|
|
3
|
+
var SmtpEmailAdapter = class {
|
|
4
|
+
constructor(config) {
|
|
5
|
+
this.config = config;
|
|
6
|
+
if (!config.smtp) throw new Error("SMTP config required");
|
|
7
|
+
this.transporter = nodemailer.createTransport({
|
|
8
|
+
host: config.smtp.host,
|
|
9
|
+
port: config.smtp.port,
|
|
10
|
+
secure: config.smtp.secure ?? false,
|
|
11
|
+
auth: {
|
|
12
|
+
user: config.smtp.username,
|
|
13
|
+
pass: config.smtp.password
|
|
14
|
+
}
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
config;
|
|
18
|
+
transporter;
|
|
19
|
+
async send(message) {
|
|
20
|
+
const from = message.from || this.config.from;
|
|
21
|
+
if (!from) throw new Error("From address required");
|
|
22
|
+
const info = await this.transporter.sendMail({
|
|
23
|
+
from: from.name ? `"${from.name}" <${from.email}>` : from.email,
|
|
24
|
+
to: Array.isArray(message.to) ? message.to.join(", ") : message.to,
|
|
25
|
+
subject: message.subject,
|
|
26
|
+
text: message.text,
|
|
27
|
+
html: message.html,
|
|
28
|
+
cc: message.cc ? Array.isArray(message.cc) ? message.cc.join(", ") : message.cc : void 0,
|
|
29
|
+
bcc: message.bcc ? Array.isArray(message.bcc) ? message.bcc.join(", ") : message.bcc : void 0,
|
|
30
|
+
replyTo: message.replyTo,
|
|
31
|
+
attachments: message.attachments?.map((a) => ({
|
|
32
|
+
filename: a.filename,
|
|
33
|
+
content: typeof a.content === "string" ? a.content : Buffer.from(a.content),
|
|
34
|
+
contentType: a.contentType
|
|
35
|
+
}))
|
|
36
|
+
});
|
|
37
|
+
return { messageId: info.messageId, success: true };
|
|
38
|
+
}
|
|
39
|
+
async sendTemplate(templateName, to, data) {
|
|
40
|
+
return this.send({
|
|
41
|
+
to,
|
|
42
|
+
subject: `Template: ${templateName}`,
|
|
43
|
+
html: `<p>Template ${templateName} rendered with ${JSON.stringify(data)}</p>`
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
export {
|
|
48
|
+
SmtpEmailAdapter
|
|
49
|
+
};
|
|
50
|
+
//# sourceMappingURL=smtp-WJDLYKD5.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/features/email/providers/smtp.ts"],"sourcesContent":["import type { EmailAdapter, EmailConfig, EmailMessage, TemplateData } from \"../index\";\nimport * as nodemailer from \"nodemailer\";\n\nexport class SmtpEmailAdapter implements EmailAdapter {\n private transporter: nodemailer.Transporter;\n\n constructor(private config: EmailConfig) {\n if (!config.smtp) throw new Error(\"SMTP config required\");\n this.transporter = nodemailer.createTransport({\n host: config.smtp.host,\n port: config.smtp.port,\n secure: config.smtp.secure ?? false,\n auth: {\n user: config.smtp.username,\n pass: config.smtp.password\n }\n });\n }\n\n async send(message: EmailMessage) {\n const from = message.from || this.config.from;\n if (!from) throw new Error(\"From address required\");\n\n const info = await this.transporter.sendMail({\n from: from.name ? `\"${from.name}\" <${from.email}>` : from.email,\n to: Array.isArray(message.to) ? message.to.join(\", \") : message.to,\n subject: message.subject,\n text: message.text,\n html: message.html,\n cc: message.cc ? (Array.isArray(message.cc) ? message.cc.join(\", \") : message.cc) : undefined,\n bcc: message.bcc ? (Array.isArray(message.bcc) ? message.bcc.join(\", \") : message.bcc) : undefined,\n replyTo: message.replyTo,\n attachments: message.attachments?.map(a => ({\n filename: a.filename,\n content: typeof a.content === 'string' ? a.content : Buffer.from(a.content),\n contentType: a.contentType\n }))\n });\n\n return { messageId: (info as any).messageId, success: true };\n }\n\n async sendTemplate(templateName: string, to: string | string[], data: TemplateData) {\n // Simple mock template engine\n return this.send({\n to,\n subject: `Template: ${templateName}`,\n html: `<p>Template ${templateName} rendered with ${JSON.stringify(data)}</p>`\n });\n }\n}\n"],"mappings":";AACA,YAAY,gBAAgB;AAErB,IAAM,mBAAN,MAA+C;AAAA,EAGlD,YAAoB,QAAqB;AAArB;AAChB,QAAI,CAAC,OAAO,KAAM,OAAM,IAAI,MAAM,sBAAsB;AACxD,SAAK,cAAyB,2BAAgB;AAAA,MAC1C,MAAM,OAAO,KAAK;AAAA,MAClB,MAAM,OAAO,KAAK;AAAA,MAClB,QAAQ,OAAO,KAAK,UAAU;AAAA,MAC9B,MAAM;AAAA,QACF,MAAM,OAAO,KAAK;AAAA,QAClB,MAAM,OAAO,KAAK;AAAA,MACtB;AAAA,IACJ,CAAC;AAAA,EACL;AAAA,EAXoB;AAAA,EAFZ;AAAA,EAeR,MAAM,KAAK,SAAuB;AAC9B,UAAM,OAAO,QAAQ,QAAQ,KAAK,OAAO;AACzC,QAAI,CAAC,KAAM,OAAM,IAAI,MAAM,uBAAuB;AAElD,UAAM,OAAO,MAAM,KAAK,YAAY,SAAS;AAAA,MACzC,MAAM,KAAK,OAAO,IAAI,KAAK,IAAI,MAAM,KAAK,KAAK,MAAM,KAAK;AAAA,MAC1D,IAAI,MAAM,QAAQ,QAAQ,EAAE,IAAI,QAAQ,GAAG,KAAK,IAAI,IAAI,QAAQ;AAAA,MAChE,SAAS,QAAQ;AAAA,MACjB,MAAM,QAAQ;AAAA,MACd,MAAM,QAAQ;AAAA,MACd,IAAI,QAAQ,KAAM,MAAM,QAAQ,QAAQ,EAAE,IAAI,QAAQ,GAAG,KAAK,IAAI,IAAI,QAAQ,KAAM;AAAA,MACpF,KAAK,QAAQ,MAAO,MAAM,QAAQ,QAAQ,GAAG,IAAI,QAAQ,IAAI,KAAK,IAAI,IAAI,QAAQ,MAAO;AAAA,MACzF,SAAS,QAAQ;AAAA,MACjB,aAAa,QAAQ,aAAa,IAAI,QAAM;AAAA,QACxC,UAAU,EAAE;AAAA,QACZ,SAAS,OAAO,EAAE,YAAY,WAAW,EAAE,UAAU,OAAO,KAAK,EAAE,OAAO;AAAA,QAC1E,aAAa,EAAE;AAAA,MACnB,EAAE;AAAA,IACN,CAAC;AAED,WAAO,EAAE,WAAY,KAAa,WAAW,SAAS,KAAK;AAAA,EAC/D;AAAA,EAEA,MAAM,aAAa,cAAsB,IAAuB,MAAoB;AAEhF,WAAO,KAAK,KAAK;AAAA,MACb;AAAA,MACA,SAAS,aAAa,YAAY;AAAA,MAClC,MAAM,eAAe,YAAY,kBAAkB,KAAK,UAAU,IAAI,CAAC;AAAA,IAC3E,CAAC;AAAA,EACL;AACJ;","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@iskra-bun/web-kit",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Servidor HTTP de Iskra basado en Hono, con Kernel y features (auth, cors, db, health, etc.).",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"iskra",
|
|
7
|
+
"bun",
|
|
8
|
+
"typescript",
|
|
9
|
+
"hono",
|
|
10
|
+
"http",
|
|
11
|
+
"web",
|
|
12
|
+
"server"
|
|
13
|
+
],
|
|
14
|
+
"author": "Joan Lascano",
|
|
15
|
+
"license": "AGPL-3.0-or-later",
|
|
16
|
+
"repository": {
|
|
17
|
+
"type": "git",
|
|
18
|
+
"url": "git+https://github.com/fearful/iskra.git",
|
|
19
|
+
"directory": "packages/web-kit"
|
|
20
|
+
},
|
|
21
|
+
"homepage": "https://github.com/fearful/iskra/tree/main/packages/web-kit#readme",
|
|
22
|
+
"bugs": "https://github.com/fearful/iskra/issues",
|
|
23
|
+
"type": "module",
|
|
24
|
+
"main": "./dist/index.js",
|
|
25
|
+
"module": "./dist/index.js",
|
|
26
|
+
"types": "./dist/index.d.ts",
|
|
27
|
+
"exports": {
|
|
28
|
+
".": {
|
|
29
|
+
"source": "./src/index.ts",
|
|
30
|
+
"bun": "./src/index.ts",
|
|
31
|
+
"types": "./dist/index.d.ts",
|
|
32
|
+
"import": "./dist/index.js",
|
|
33
|
+
"default": "./dist/index.js"
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
"files": [
|
|
37
|
+
"dist",
|
|
38
|
+
"src",
|
|
39
|
+
"README.md",
|
|
40
|
+
"CHANGELOG.md"
|
|
41
|
+
],
|
|
42
|
+
"publishConfig": {
|
|
43
|
+
"access": "public"
|
|
44
|
+
},
|
|
45
|
+
"scripts": {
|
|
46
|
+
"test": "bun test",
|
|
47
|
+
"build": "tsup --config ../../tsup.config.ts"
|
|
48
|
+
},
|
|
49
|
+
"dependencies": {
|
|
50
|
+
"@hono/zod-openapi": "^1.2.0",
|
|
51
|
+
"@hono/zod-validator": "^0.4.2",
|
|
52
|
+
"@iskra-bun/core": "0.1.0",
|
|
53
|
+
"better-auth": "^1.4.18",
|
|
54
|
+
"drizzle-orm": "^0.45.1",
|
|
55
|
+
"hono": "^4.6.14",
|
|
56
|
+
"ioredis": "^5.8.2",
|
|
57
|
+
"mysql2": "^3.16.0",
|
|
58
|
+
"postgres": "^3.4.7",
|
|
59
|
+
"ajv": "^8.18.0",
|
|
60
|
+
"ajv-errors": "^3.0.0",
|
|
61
|
+
"ajv-formats": "^3.0.1",
|
|
62
|
+
"zod": "^3.24.1",
|
|
63
|
+
"@aws-sdk/client-s3": "^3.600.0",
|
|
64
|
+
"@aws-sdk/s3-request-presigner": "^3.600.0",
|
|
65
|
+
"@hono/otel": "^1.0.0",
|
|
66
|
+
"nodemailer": "^6.9.1",
|
|
67
|
+
"@sendgrid/mail": "^8.1.0"
|
|
68
|
+
},
|
|
69
|
+
"devDependencies": {
|
|
70
|
+
"@types/bun": "^1.3.5",
|
|
71
|
+
"@types/node": "^22.10.2",
|
|
72
|
+
"@types/nodemailer": "^7.0.4"
|
|
73
|
+
}
|
|
74
|
+
}
|
package/src/driver.ts
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { App, Driver } from '@iskra-bun/core';
|
|
2
|
+
import { Hono } from 'hono';
|
|
3
|
+
import { Kernel } from './kernel';
|
|
4
|
+
import type { Feature, KernelConfig } from './types';
|
|
5
|
+
|
|
6
|
+
export interface WebPluginConfig extends KernelConfig {
|
|
7
|
+
router?: Hono;
|
|
8
|
+
features?: Feature[];
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export class WebPlugin implements Driver {
|
|
12
|
+
name = 'WebPlugin';
|
|
13
|
+
private app: App | null = null;
|
|
14
|
+
private kernel: Kernel;
|
|
15
|
+
private runningServer: any;
|
|
16
|
+
private router?: Hono;
|
|
17
|
+
|
|
18
|
+
constructor(config: WebPluginConfig = {}) {
|
|
19
|
+
const { router, features, ...kernelConfig } = config;
|
|
20
|
+
this.kernel = new Kernel(kernelConfig);
|
|
21
|
+
this.router = router;
|
|
22
|
+
|
|
23
|
+
if (features) {
|
|
24
|
+
for (const feature of features) {
|
|
25
|
+
this.kernel.registerFeature(feature);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async init(app: App) {
|
|
31
|
+
this.app = app;
|
|
32
|
+
await this.kernel.initialize();
|
|
33
|
+
|
|
34
|
+
if (this.router) {
|
|
35
|
+
this.kernel.getApp().route('/', this.router);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
getHonoApp(): Hono {
|
|
40
|
+
return this.kernel.getApp();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async start() {
|
|
44
|
+
this.app?.logger.info(`Starting WebPlugin...`);
|
|
45
|
+
// We let the kernel start or we start it manually using Bun
|
|
46
|
+
// Kernel.start() does Bun.serve.
|
|
47
|
+
await this.kernel.start();
|
|
48
|
+
this.runningServer = true; // Placeholder, Kernel.start() might block or not return server instance directly
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async stop() {
|
|
52
|
+
await this.kernel.shutdown();
|
|
53
|
+
this.app?.logger.info('WebPlugin stopped');
|
|
54
|
+
}
|
|
55
|
+
}
|
package/src/errors.ts
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { IskraError, ErrorCodes, type ErrorCode } from '@iskra-bun/core';
|
|
2
|
+
import { HTTPException } from 'hono/http-exception';
|
|
3
|
+
|
|
4
|
+
// ─── HTTP Errors ─────────────────────────────────────────────────────────────
|
|
5
|
+
|
|
6
|
+
export class HttpError extends IskraError {
|
|
7
|
+
public readonly status: number;
|
|
8
|
+
|
|
9
|
+
constructor(status: number, message: string, options?: { code?: ErrorCode; cause?: Error; context?: Record<string, unknown> }) {
|
|
10
|
+
super(message, { code: options?.code ?? ErrorCodes.INTERNAL_ERROR, cause: options?.cause, context: options?.context });
|
|
11
|
+
this.name = 'HttpError';
|
|
12
|
+
this.status = status;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
toHTTPException(): HTTPException {
|
|
16
|
+
return new HTTPException(this.status as any, { message: this.message, cause: this });
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// ─── Validation Error ────────────────────────────────────────────────────────
|
|
21
|
+
|
|
22
|
+
export class ValidationError extends HttpError {
|
|
23
|
+
public readonly details: unknown;
|
|
24
|
+
|
|
25
|
+
constructor(message: string, details?: unknown, options?: { cause?: Error; context?: Record<string, unknown> }) {
|
|
26
|
+
super(400, message, { code: ErrorCodes.VALIDATION_ERROR, ...options });
|
|
27
|
+
this.name = 'ValidationError';
|
|
28
|
+
this.details = details;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// ─── Auth Error ──────────────────────────────────────────────────────────────
|
|
33
|
+
|
|
34
|
+
export class AuthError extends HttpError {
|
|
35
|
+
constructor(message: string = 'Unauthorized', options?: { cause?: Error; context?: Record<string, unknown> }) {
|
|
36
|
+
super(401, message, { code: ErrorCodes.UNAUTHORIZED, ...options });
|
|
37
|
+
this.name = 'AuthError';
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// ─── Forbidden Error ─────────────────────────────────────────────────────────
|
|
42
|
+
|
|
43
|
+
export class ForbiddenError extends HttpError {
|
|
44
|
+
constructor(message: string = 'Forbidden', options?: { cause?: Error; context?: Record<string, unknown> }) {
|
|
45
|
+
super(403, message, { code: ErrorCodes.FORBIDDEN, ...options });
|
|
46
|
+
this.name = 'ForbiddenError';
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// ─── Not Found Error ─────────────────────────────────────────────────────────
|
|
51
|
+
|
|
52
|
+
export class NotFoundError extends HttpError {
|
|
53
|
+
constructor(message: string = 'Not Found', options?: { cause?: Error; context?: Record<string, unknown> }) {
|
|
54
|
+
super(404, message, { code: ErrorCodes.NOT_FOUND, ...options });
|
|
55
|
+
this.name = 'NotFoundError';
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// ─── Conflict Error ──────────────────────────────────────────────────────────
|
|
60
|
+
|
|
61
|
+
export class ConflictError extends HttpError {
|
|
62
|
+
constructor(message: string = 'Conflict', options?: { cause?: Error; context?: Record<string, unknown> }) {
|
|
63
|
+
super(409, message, { code: ErrorCodes.CONFLICT, ...options });
|
|
64
|
+
this.name = 'ConflictError';
|
|
65
|
+
}
|
|
66
|
+
}
|