@iskra-bun/web-kit 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -6,16 +6,25 @@ export class HealthCheckFeature implements Feature {
6
6
  name = "health";
7
7
 
8
8
  private kernel?: Kernel;
9
- private config: Required<Omit<HealthCheckConfig, "checks">> & { checks?: HealthCheckConfig["checks"] };
9
+ private config: Required<Omit<HealthCheckConfig, "checks" | "readinessChecks">> & {
10
+ checks?: HealthCheckConfig["checks"];
11
+ };
12
+ private readinessChecks: Map<string, () => Promise<boolean>>;
10
13
 
11
14
  constructor(config: HealthCheckConfig = {}) {
12
15
  this.config = {
13
16
  path: config.path || "/health",
14
17
  readinessPath: config.readinessPath || "/health/ready",
15
18
  livenessPath: config.livenessPath || "/health/live",
16
- includeDetails: config.includeDetails !== undefined ? config.includeDetails : true,
19
+ includeDetails: config.includeDetails !== undefined ? config.includeDetails : false,
17
20
  checks: config.checks,
18
21
  };
22
+ const initial = config.readinessChecks ?? {};
23
+ this.readinessChecks = new Map(Object.entries(initial));
24
+ }
25
+
26
+ addReadinessCheck(name: string, check: () => Promise<boolean>): void {
27
+ this.readinessChecks = new Map([...this.readinessChecks, [name, check]]);
19
28
  }
20
29
 
21
30
  async initialize(kernel: Kernel): Promise<void> {
@@ -56,7 +65,10 @@ export class HealthCheckFeature implements Feature {
56
65
  try {
57
66
  customChecks[name] = await check(c);
58
67
  } catch (error) {
59
- customChecks[name] = { status: "error", error: String(error) };
68
+ // Log the detail server-side; never serialize the raw error
69
+ // (it may embed connection strings or other secrets) to the client.
70
+ console.error(`Health custom check "${name}" failed:`, error);
71
+ customChecks[name] = { status: "error" };
60
72
  }
61
73
  }
62
74
  response.customChecks = customChecks;
@@ -75,13 +87,37 @@ export class HealthCheckFeature implements Feature {
75
87
  report[key] = { status: "ok" };
76
88
  }
77
89
  } catch (e) {
78
- report[key] = { status: "error", error: String(e) };
90
+ // Log server-side; return only a generic status so DB/cache error
91
+ // strings (which can carry connection details) never reach the client.
92
+ console.error(`Health feature check "${key}" failed:`, e);
93
+ report[key] = { status: "error" };
79
94
  }
80
95
  }
81
96
 
82
97
  private async handleReadinessCheck(c: Context) {
83
- // Simplified readiness check
84
- return c.json({ status: "ready" });
98
+ if (this.readinessChecks.size === 0) {
99
+ return c.json({ status: "ready" });
100
+ }
101
+
102
+ const results: Record<string, boolean> = {};
103
+ const failed: string[] = [];
104
+
105
+ for (const [name, check] of this.readinessChecks) {
106
+ try {
107
+ const passed = await check();
108
+ results[name] = passed;
109
+ if (!passed) failed.push(name);
110
+ } catch {
111
+ results[name] = false;
112
+ failed.push(name);
113
+ }
114
+ }
115
+
116
+ if (failed.length > 0) {
117
+ return c.json({ status: "not ready", checks: results, failed }, 503);
118
+ }
119
+
120
+ return c.json({ status: "ready", checks: results });
85
121
  }
86
122
 
87
123
  private async handleLivenessCheck(c: Context) {
@@ -1,12 +1,11 @@
1
1
  import type { Feature } from "../../types";
2
2
  import type { Kernel } from "../../kernel";
3
- import type { StorageConfig } from "./base";
4
- import { BaseStorageAdapter } from "./base";
3
+ import type { StorageConfig } from "@iskra-bun/storage-kit";
4
+ import { BaseStorageAdapter, createStorageAdapter } from "@iskra-bun/storage-kit";
5
5
  import type { Context, Next } from "hono";
6
- import { LocalStorageAdapter } from "./adapters/local";
7
6
 
8
- export { BaseStorageAdapter, LocalStorageAdapter };
9
- export type { StorageConfig, StorageFile } from "./base";
7
+ export { BaseStorageAdapter, LocalStorageAdapter } from "@iskra-bun/storage-kit";
8
+ export type { StorageConfig, StorageFile } from "@iskra-bun/storage-kit";
10
9
 
11
10
  declare module "hono" {
12
11
  interface ContextVariableMap {
@@ -21,15 +20,7 @@ export class StorageFeature implements Feature {
21
20
  constructor(private config: StorageConfig) { }
22
21
 
23
22
  async initialize(kernel: Kernel): Promise<void> {
24
- if (this.config.adapter === "local") {
25
- this.adapter = new LocalStorageAdapter(this.config);
26
- } else if (this.config.adapter === "s3" || this.config.adapter === "minio") {
27
- const { S3StorageAdapter } = await import("./adapters/s3");
28
- this.adapter = new S3StorageAdapter(this.config);
29
- } else {
30
- throw new Error(`Adapter ${this.config.adapter} not supported.`);
31
- }
32
-
23
+ this.adapter = await createStorageAdapter(this.config);
33
24
  await this.adapter.connect();
34
25
 
35
26
  const app = kernel.getApp();
@@ -1,5 +1,5 @@
1
1
  import type { StorageFeature } from "../storage";
2
- import type { BaseStorageAdapter, PutOptions } from "../storage/base";
2
+ import type { BaseStorageAdapter, PutOptions } from "@iskra-bun/storage-kit";
3
3
 
4
4
  export interface UploadOptions {
5
5
  metadata?: Record<string, string>;
@@ -24,6 +24,16 @@ export class UploadHelper {
24
24
  return this.basePath;
25
25
  }
26
26
 
27
+ // Defense-in-depth: reduce an attacker-controlled filename to a safe basename
28
+ // and strip it to an allowlisted charset so traversal segments ("../",
29
+ // "..\\", absolute paths) can never escape the project base path. storage-kit's
30
+ // sanitizePath remains the primary control; this is a second, independent gate.
31
+ private safeBasename(filename: string): string {
32
+ const base = filename.split(/[\\/]/).pop() || "";
33
+ const cleaned = base.replace(/[^a-zA-Z0-9._-]/g, "_").replace(/^\.+/, "");
34
+ return cleaned.length > 0 ? cleaned : "file";
35
+ }
36
+
27
37
  private buildPath(filename: string, subfolder?: string): string {
28
38
  if (subfolder) {
29
39
  const normalized = subfolder.replace(/^\/+|\/+$/g, "");
@@ -60,7 +70,8 @@ export class UploadHelper {
60
70
 
61
71
  const data = new Uint8Array(await file.arrayBuffer());
62
72
  const opts = { ...options, contentType: options?.contentType || file.type };
63
- return this.upload(file.name, data, subfolder, opts);
73
+ const safeName = this.safeBasename(file.name);
74
+ return this.upload(safeName, data, subfolder, opts);
64
75
  }
65
76
 
66
77
  async list(subfolder?: string) {
package/src/server.ts CHANGED
@@ -28,10 +28,23 @@ export class WebDriver implements Driver {
28
28
 
29
29
  init(app: App) {
30
30
  this.app = app;
31
+ this.setupSecurityHeaders();
31
32
  this.setupRoutes();
32
33
  this.setupOpenApi();
33
34
  }
34
35
 
36
+ // Apply the same standard security headers as the Kernel HTTP stack
37
+ // (web-kit/src/kernel.ts) so the standalone WebDriver server is at parity.
38
+ private setupSecurityHeaders() {
39
+ this.server.use('*', async (c, next) => {
40
+ await next();
41
+ c.res.headers.set('X-Frame-Options', 'SAMEORIGIN');
42
+ c.res.headers.set('X-Content-Type-Options', 'nosniff');
43
+ c.res.headers.set('X-XSS-Protection', '1; mode=block');
44
+ c.res.headers.set('Referrer-Policy', 'strict-origin-when-cross-origin');
45
+ });
46
+ }
47
+
35
48
  private setupOpenApi() {
36
49
  if (this.options.openApi) {
37
50
  this.server.doc(this.options.openApi.path, {
@@ -108,8 +121,11 @@ export class WebDriver implements Driver {
108
121
  if (result instanceof Response) return result;
109
122
  return c.json(result);
110
123
  } catch (err: any) {
124
+ // Log the detail server-side; never serialize the raw error
125
+ // message (it may embed connection strings or other secrets)
126
+ // to the client. Return only a generic body.
111
127
  this.app.logger.error(err);
112
- return c.json({ error: err.message }, 500);
128
+ return c.json({ error: 'Internal Server Error' }, 500);
113
129
  }
114
130
  });
115
131
  }
package/src/types.ts CHANGED
@@ -132,6 +132,9 @@ export interface HealthCheckConfig {
132
132
  details?: any;
133
133
  }>;
134
134
  };
135
+ readinessChecks?: {
136
+ [name: string]: () => Promise<boolean>;
137
+ };
135
138
  }
136
139
 
137
140
  export interface RequestIdConfig {
@@ -1,51 +0,0 @@
1
- // src/features/storage/base.ts
2
- var BaseStorageAdapter = class {
3
- connected = false;
4
- isConnected() {
5
- return this.connected;
6
- }
7
- ensureConnected() {
8
- if (!this.connected) {
9
- throw new Error("Storage not connected. Call connect() first.");
10
- }
11
- }
12
- async copy(from, to) {
13
- const data = await this.get(from);
14
- if (!data) {
15
- throw new Error(`Source file not found: ${from}`);
16
- }
17
- await this.put(to, data);
18
- }
19
- async move(from, to) {
20
- await this.copy(from, to);
21
- await this.delete(from);
22
- }
23
- generateFileName(originalName) {
24
- const ext = originalName.split(".").pop();
25
- const timestamp = Date.now();
26
- const random = Math.random().toString(36).substring(2, 15);
27
- return `${timestamp}-${random}.${ext}`;
28
- }
29
- sanitizePath(path) {
30
- return path.replace(/^\/+/, "").replace(/\/+/g, "/").replace(/\\/g, "/");
31
- }
32
- getMimeType(filename) {
33
- const ext = filename.split(".").pop()?.toLowerCase();
34
- const mimeTypes = {
35
- jpg: "image/jpeg",
36
- jpeg: "image/jpeg",
37
- png: "image/png",
38
- gif: "image/gif",
39
- pdf: "application/pdf",
40
- txt: "text/plain",
41
- json: "application/json",
42
- zip: "application/zip"
43
- };
44
- return mimeTypes[ext || ""] || "application/octet-stream";
45
- }
46
- };
47
-
48
- export {
49
- BaseStorageAdapter
50
- };
51
- //# sourceMappingURL=chunk-POXNRNTC.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/features/storage/base.ts"],"sourcesContent":["import fs from \"node:fs/promises\";\n\nexport interface StorageConfig {\n adapter: \"local\" | \"minio\" | \"s3\";\n basePath?: string; // For local storage\n connection?: {\n endpoint?: string;\n accessKey?: string;\n secretKey?: string;\n bucket?: string;\n region?: string;\n useSSL?: boolean;\n };\n}\n\nexport interface StorageFile {\n name: string;\n path: string;\n size: number;\n mimeType?: string;\n lastModified?: Date;\n url?: string;\n}\n\nexport interface PutOptions {\n contentType?: string;\n metadata?: Record<string, string>;\n public?: boolean;\n}\n\nexport interface StorageAdapter {\n connect(): Promise<void>;\n disconnect(): Promise<void>;\n put(path: string, data: Uint8Array | Buffer | ReadableStream, options?: PutOptions): Promise<StorageFile>;\n get(path: string): Promise<Uint8Array | null>;\n // getStream(path: string): Promise<ReadableStream | null>; \n // Node streams are different, sticking to Buffer/Uint8Array for simplicity or using generic Stream\n delete(path: string): Promise<void>;\n exists(path: string): Promise<boolean>;\n list(prefix?: string): Promise<StorageFile[]>;\n url(path: string, expiresIn?: number): Promise<string>;\n copy(from: string, to: string): Promise<void>;\n move(from: string, to: string): Promise<void>;\n isDirectory(path: string): Promise<boolean>;\n}\n\nexport abstract class BaseStorageAdapter implements StorageAdapter {\n protected connected = false;\n\n abstract connect(): Promise<void>;\n abstract disconnect(): Promise<void>;\n abstract put(path: string, data: Uint8Array | Buffer | ReadableStream, options?: PutOptions): Promise<StorageFile>;\n abstract get(path: string): Promise<Uint8Array | null>;\n\n abstract delete(path: string): Promise<void>;\n abstract exists(path: string): Promise<boolean>;\n abstract list(prefix?: string): Promise<StorageFile[]>;\n abstract url(path: string, expiresIn?: number): Promise<string>;\n abstract isDirectory(path: string): Promise<boolean>;\n\n isConnected(): boolean {\n return this.connected;\n }\n\n protected ensureConnected(): void {\n if (!this.connected) {\n throw new Error(\"Storage not connected. Call connect() first.\");\n }\n }\n\n async copy(from: string, to: string): Promise<void> {\n const data = await this.get(from);\n if (!data) {\n throw new Error(`Source file not found: ${from}`);\n }\n await this.put(to, data);\n }\n\n async move(from: string, to: string): Promise<void> {\n await this.copy(from, to);\n await this.delete(from);\n }\n\n protected generateFileName(originalName: string): string {\n const ext = originalName.split(\".\").pop();\n const timestamp = Date.now();\n const random = Math.random().toString(36).substring(2, 15);\n return `${timestamp}-${random}.${ext}`;\n }\n\n protected sanitizePath(path: string): string {\n return path\n .replace(/^\\/+/, \"\")\n .replace(/\\/+/g, \"/\")\n .replace(/\\\\/g, \"/\");\n }\n\n protected getMimeType(filename: string): string {\n const ext = filename.split(\".\").pop()?.toLowerCase();\n const mimeTypes: Record<string, string> = {\n jpg: \"image/jpeg\",\n jpeg: \"image/jpeg\",\n png: \"image/png\",\n gif: \"image/gif\",\n pdf: \"application/pdf\",\n txt: \"text/plain\",\n json: \"application/json\",\n zip: \"application/zip\",\n };\n return mimeTypes[ext || \"\"] || \"application/octet-stream\";\n }\n}\n"],"mappings":";AA8CO,IAAe,qBAAf,MAA4D;AAAA,EACrD,YAAY;AAAA,EAatB,cAAuB;AACnB,WAAO,KAAK;AAAA,EAChB;AAAA,EAEU,kBAAwB;AAC9B,QAAI,CAAC,KAAK,WAAW;AACjB,YAAM,IAAI,MAAM,8CAA8C;AAAA,IAClE;AAAA,EACJ;AAAA,EAEA,MAAM,KAAK,MAAc,IAA2B;AAChD,UAAM,OAAO,MAAM,KAAK,IAAI,IAAI;AAChC,QAAI,CAAC,MAAM;AACP,YAAM,IAAI,MAAM,0BAA0B,IAAI,EAAE;AAAA,IACpD;AACA,UAAM,KAAK,IAAI,IAAI,IAAI;AAAA,EAC3B;AAAA,EAEA,MAAM,KAAK,MAAc,IAA2B;AAChD,UAAM,KAAK,KAAK,MAAM,EAAE;AACxB,UAAM,KAAK,OAAO,IAAI;AAAA,EAC1B;AAAA,EAEU,iBAAiB,cAA8B;AACrD,UAAM,MAAM,aAAa,MAAM,GAAG,EAAE,IAAI;AACxC,UAAM,YAAY,KAAK,IAAI;AAC3B,UAAM,SAAS,KAAK,OAAO,EAAE,SAAS,EAAE,EAAE,UAAU,GAAG,EAAE;AACzD,WAAO,GAAG,SAAS,IAAI,MAAM,IAAI,GAAG;AAAA,EACxC;AAAA,EAEU,aAAa,MAAsB;AACzC,WAAO,KACF,QAAQ,QAAQ,EAAE,EAClB,QAAQ,QAAQ,GAAG,EACnB,QAAQ,OAAO,GAAG;AAAA,EAC3B;AAAA,EAEU,YAAY,UAA0B;AAC5C,UAAM,MAAM,SAAS,MAAM,GAAG,EAAE,IAAI,GAAG,YAAY;AACnD,UAAM,YAAoC;AAAA,MACtC,KAAK;AAAA,MACL,MAAM;AAAA,MACN,KAAK;AAAA,MACL,KAAK;AAAA,MACL,KAAK;AAAA,MACL,KAAK;AAAA,MACL,MAAM;AAAA,MACN,KAAK;AAAA,IACT;AACA,WAAO,UAAU,OAAO,EAAE,KAAK;AAAA,EACnC;AACJ;","names":[]}
@@ -1,83 +0,0 @@
1
- // src/features/email/providers/mailgun.ts
2
- var MailgunEmailAdapter = class {
3
- apiKey;
4
- domain;
5
- baseUrl;
6
- defaultFrom;
7
- constructor(config) {
8
- if (!config.apiKey) throw new Error("Mailgun requires apiKey");
9
- if (!config.domain) throw new Error("Mailgun requires domain");
10
- this.apiKey = config.apiKey;
11
- this.domain = config.domain;
12
- this.baseUrl = config.baseUrl || "https://api.mailgun.net/v3";
13
- this.defaultFrom = config.from;
14
- }
15
- async send(message) {
16
- const form = new FormData();
17
- const from = message.from || this.defaultFrom;
18
- if (from) {
19
- form.append("from", from.name ? `${from.name} <${from.email}>` : from.email);
20
- }
21
- const to = Array.isArray(message.to) ? message.to.join(",") : message.to;
22
- form.append("to", to);
23
- form.append("subject", message.subject);
24
- if (message.text) form.append("text", message.text);
25
- if (message.html) form.append("html", message.html);
26
- if (message.cc) form.append("cc", Array.isArray(message.cc) ? message.cc.join(",") : message.cc);
27
- if (message.bcc) form.append("bcc", Array.isArray(message.bcc) ? message.bcc.join(",") : message.bcc);
28
- if (message.replyTo) form.append("h:Reply-To", message.replyTo);
29
- if (message.headers) {
30
- for (const [key, value] of Object.entries(message.headers)) {
31
- form.append(`h:${key}`, value);
32
- }
33
- }
34
- if (message.attachments) {
35
- for (const att of message.attachments) {
36
- const content = typeof att.content === "string" ? new TextEncoder().encode(att.content) : att.content;
37
- const bytes = new Uint8Array(content);
38
- const blob = new Blob([bytes], { type: att.contentType || "application/octet-stream" });
39
- form.append("attachment", blob, att.filename);
40
- }
41
- }
42
- const response = await fetch(`${this.baseUrl}/${this.domain}/messages`, {
43
- method: "POST",
44
- headers: {
45
- Authorization: "Basic " + btoa(`api:${this.apiKey}`)
46
- },
47
- body: form
48
- });
49
- if (!response.ok) {
50
- const errorText = await response.text();
51
- throw new Error(`Mailgun API error (${response.status}): ${errorText}`);
52
- }
53
- const result = await response.json();
54
- return { messageId: result.id, success: true };
55
- }
56
- async sendTemplate(templateName, to, data) {
57
- const form = new FormData();
58
- if (this.defaultFrom) {
59
- const from = this.defaultFrom;
60
- form.append("from", from.name ? `${from.name} <${from.email}>` : from.email);
61
- }
62
- form.append("to", Array.isArray(to) ? to.join(",") : to);
63
- form.append("template", templateName);
64
- form.append("h:X-Mailgun-Variables", JSON.stringify(data));
65
- const response = await fetch(`${this.baseUrl}/${this.domain}/messages`, {
66
- method: "POST",
67
- headers: {
68
- Authorization: "Basic " + btoa(`api:${this.apiKey}`)
69
- },
70
- body: form
71
- });
72
- if (!response.ok) {
73
- const errorText = await response.text();
74
- throw new Error(`Mailgun API error (${response.status}): ${errorText}`);
75
- }
76
- const result = await response.json();
77
- return { messageId: result.id, success: true };
78
- }
79
- };
80
- export {
81
- MailgunEmailAdapter
82
- };
83
- //# sourceMappingURL=mailgun-Z46GZJNI.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/features/email/providers/mailgun.ts"],"sourcesContent":["import type { EmailAdapter, EmailMessage, EmailConfig, TemplateData } from \"../index\";\n\nexport class MailgunEmailAdapter implements EmailAdapter {\n private apiKey: string;\n private domain: string;\n private baseUrl: string;\n private defaultFrom?: { name?: string; email: string };\n\n constructor(config: EmailConfig) {\n if (!config.apiKey) throw new Error(\"Mailgun requires apiKey\");\n if (!config.domain) throw new Error(\"Mailgun requires domain\");\n\n this.apiKey = config.apiKey;\n this.domain = config.domain;\n this.baseUrl = config.baseUrl || \"https://api.mailgun.net/v3\";\n this.defaultFrom = config.from;\n }\n\n async send(message: EmailMessage): Promise<{ messageId: string; success: boolean }> {\n const form = new FormData();\n\n const from = message.from || this.defaultFrom;\n if (from) {\n form.append(\"from\", from.name ? `${from.name} <${from.email}>` : from.email);\n }\n\n const to = Array.isArray(message.to) ? message.to.join(\",\") : message.to;\n form.append(\"to\", to);\n form.append(\"subject\", message.subject);\n\n if (message.text) form.append(\"text\", message.text);\n if (message.html) form.append(\"html\", message.html);\n if (message.cc) form.append(\"cc\", Array.isArray(message.cc) ? message.cc.join(\",\") : message.cc);\n if (message.bcc) form.append(\"bcc\", Array.isArray(message.bcc) ? message.bcc.join(\",\") : message.bcc);\n if (message.replyTo) form.append(\"h:Reply-To\", message.replyTo);\n\n if (message.headers) {\n for (const [key, value] of Object.entries(message.headers)) {\n form.append(`h:${key}`, value);\n }\n }\n\n if (message.attachments) {\n for (const att of message.attachments) {\n const content = typeof att.content === \"string\"\n ? new TextEncoder().encode(att.content)\n : att.content;\n const bytes = new Uint8Array(content);\n const blob = new Blob([bytes], { type: att.contentType || \"application/octet-stream\" });\n form.append(\"attachment\", blob, att.filename);\n }\n }\n\n const response = await fetch(`${this.baseUrl}/${this.domain}/messages`, {\n method: \"POST\",\n headers: {\n Authorization: \"Basic \" + btoa(`api:${this.apiKey}`),\n },\n body: form,\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n throw new Error(`Mailgun API error (${response.status}): ${errorText}`);\n }\n\n const result = await response.json() as { id: string; message: string };\n return { messageId: result.id, success: true };\n }\n\n async sendTemplate(templateName: string, to: string | string[], data: TemplateData): Promise<{ messageId: string; success: boolean }> {\n const form = new FormData();\n\n if (this.defaultFrom) {\n const from = this.defaultFrom;\n form.append(\"from\", from.name ? `${from.name} <${from.email}>` : from.email);\n }\n\n form.append(\"to\", Array.isArray(to) ? to.join(\",\") : to);\n form.append(\"template\", templateName);\n form.append(\"h:X-Mailgun-Variables\", JSON.stringify(data));\n\n const response = await fetch(`${this.baseUrl}/${this.domain}/messages`, {\n method: \"POST\",\n headers: {\n Authorization: \"Basic \" + btoa(`api:${this.apiKey}`),\n },\n body: form,\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n throw new Error(`Mailgun API error (${response.status}): ${errorText}`);\n }\n\n const result = await response.json() as { id: string; message: string };\n return { messageId: result.id, success: true };\n }\n}\n"],"mappings":"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","names":[]}
@@ -1,171 +0,0 @@
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
@@ -1 +0,0 @@
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":[]}
@@ -1,43 +0,0 @@
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
@@ -1 +0,0 @@
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":[]}
@@ -1,50 +0,0 @@
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