@beeblock/svelar 0.4.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/LICENSE +21 -0
- package/README.md +110 -0
- package/dist/actions/index.d.ts +101 -0
- package/dist/actions/index.js +1 -0
- package/dist/api-keys/index.d.ts +58 -0
- package/dist/api-keys/index.js +1 -0
- package/dist/audit/index.d.ts +52 -0
- package/dist/audit/index.js +1 -0
- package/dist/auth/Auth.d.ts +283 -0
- package/dist/auth/Gate.d.ts +166 -0
- package/dist/auth/index.d.ts +2 -0
- package/dist/auth/index.js +80 -0
- package/dist/broadcasting/client.d.ts +195 -0
- package/dist/broadcasting/client.js +1 -0
- package/dist/broadcasting/index.d.ts +318 -0
- package/dist/broadcasting/index.js +20 -0
- package/dist/cache/index.d.ts +77 -0
- package/dist/cache/index.js +1 -0
- package/dist/cli/Cli.d.ts +23 -0
- package/dist/cli/Command.d.ts +36 -0
- package/dist/cli/bin.d.ts +8 -0
- package/dist/cli/bin.js +5856 -0
- package/dist/cli/commands/KeyGenerateCommand.d.ts +16 -0
- package/dist/cli/commands/MakeActionCommand.d.ts +15 -0
- package/dist/cli/commands/MakeBroadcastingCommand.d.ts +29 -0
- package/dist/cli/commands/MakeChannelCommand.d.ts +18 -0
- package/dist/cli/commands/MakeCommandCommand.d.ts +16 -0
- package/dist/cli/commands/MakeConfigCommand.d.ts +13 -0
- package/dist/cli/commands/MakeControllerCommand.d.ts +28 -0
- package/dist/cli/commands/MakeDashboardCommand.d.ts +34 -0
- package/dist/cli/commands/MakeDockerCommand.d.ts +32 -0
- package/dist/cli/commands/MakeEventCommand.d.ts +11 -0
- package/dist/cli/commands/MakeJobCommand.d.ts +11 -0
- package/dist/cli/commands/MakeListenerCommand.d.ts +16 -0
- package/dist/cli/commands/MakeMiddlewareCommand.d.ts +11 -0
- package/dist/cli/commands/MakeMigrationCommand.d.ts +17 -0
- package/dist/cli/commands/MakeModelCommand.d.ts +25 -0
- package/dist/cli/commands/MakeObserverCommand.d.ts +23 -0
- package/dist/cli/commands/MakePluginCommand.d.ts +11 -0
- package/dist/cli/commands/MakeProviderCommand.d.ts +11 -0
- package/dist/cli/commands/MakeRepositoryCommand.d.ts +22 -0
- package/dist/cli/commands/MakeRequestCommand.d.ts +15 -0
- package/dist/cli/commands/MakeResourceCommand.d.ts +30 -0
- package/dist/cli/commands/MakeRouteCommand.d.ts +42 -0
- package/dist/cli/commands/MakeSchemaCommand.d.ts +20 -0
- package/dist/cli/commands/MakeSeederCommand.d.ts +11 -0
- package/dist/cli/commands/MakeServiceCommand.d.ts +28 -0
- package/dist/cli/commands/MakeTaskCommand.d.ts +12 -0
- package/dist/cli/commands/MigrateCommand.d.ts +26 -0
- package/dist/cli/commands/NewCommand.d.ts +21 -0
- package/dist/cli/commands/NewCommandTemplates.d.ts +123 -0
- package/dist/cli/commands/PluginInstallCommand.d.ts +16 -0
- package/dist/cli/commands/PluginListCommand.d.ts +11 -0
- package/dist/cli/commands/PluginPublishCommand.d.ts +22 -0
- package/dist/cli/commands/QueueFailedCommand.d.ts +9 -0
- package/dist/cli/commands/QueueFlushCommand.d.ts +9 -0
- package/dist/cli/commands/QueueRetryCommand.d.ts +16 -0
- package/dist/cli/commands/QueueWorkCommand.d.ts +25 -0
- package/dist/cli/commands/RoutesListCommand.d.ts +30 -0
- package/dist/cli/commands/ScheduleRunCommand.d.ts +15 -0
- package/dist/cli/commands/SeedCommand.d.ts +14 -0
- package/dist/cli/commands/TinkerCommand.d.ts +10 -0
- package/dist/cli/index.d.ts +36 -0
- package/dist/cli/index.js +1973 -0
- package/dist/cli/ts-resolve-hook.mjs +74 -0
- package/dist/cli/ts-resolver.mjs +8 -0
- package/dist/config/Config.d.ts +65 -0
- package/dist/config/index.d.ts +1 -0
- package/dist/config/index.js +1 -0
- package/dist/container/Application.d.ts +33 -0
- package/dist/container/Container.d.ts +70 -0
- package/dist/container/ServiceProvider.d.ts +21 -0
- package/dist/container/index.d.ts +3 -0
- package/dist/container/index.js +1 -0
- package/dist/dashboard/index.d.ts +123 -0
- package/dist/dashboard/index.js +5 -0
- package/dist/database/Connection.d.ts +80 -0
- package/dist/database/Migration.d.ts +76 -0
- package/dist/database/SchemaBuilder.d.ts +91 -0
- package/dist/database/Seeder.d.ts +9 -0
- package/dist/database/index.d.ts +4 -0
- package/dist/database/index.js +4 -0
- package/dist/email-templates/index.d.ts +51 -0
- package/dist/email-templates/index.js +57 -0
- package/dist/errors/Handler.d.ts +100 -0
- package/dist/errors/index.d.ts +1 -0
- package/dist/errors/index.js +5 -0
- package/dist/events/EventServiceProvider.d.ts +82 -0
- package/dist/events/Listener.d.ts +28 -0
- package/dist/events/index.d.ts +80 -0
- package/dist/events/index.js +1 -0
- package/dist/excel/index.d.ts +154 -0
- package/dist/excel/index.js +1 -0
- package/dist/feature-flags/index.d.ts +158 -0
- package/dist/feature-flags/index.js +59 -0
- package/dist/forms/index.d.ts +81 -0
- package/dist/forms/index.js +1 -0
- package/dist/hashing/Hash.d.ts +51 -0
- package/dist/hashing/index.d.ts +1 -0
- package/dist/hashing/index.js +1 -0
- package/dist/hooks/index.d.ts +135 -0
- package/dist/hooks/index.js +5 -0
- package/dist/http/index.d.ts +201 -0
- package/dist/http/index.js +2 -0
- package/dist/i18n/index.d.ts +81 -0
- package/dist/i18n/index.js +1 -0
- package/dist/index.d.ts +54 -0
- package/dist/index.js +127 -0
- package/dist/logging/LogViewer.d.ts +95 -0
- package/dist/logging/LogViewer.js +1 -0
- package/dist/logging/index.d.ts +83 -0
- package/dist/logging/index.js +3 -0
- package/dist/mail/index.d.ts +149 -0
- package/dist/mail/index.js +1 -0
- package/dist/middleware/Middleware.d.ts +208 -0
- package/dist/middleware/index.d.ts +1 -0
- package/dist/middleware/index.js +1 -0
- package/dist/notifications/index.d.ts +85 -0
- package/dist/notifications/index.js +2 -0
- package/dist/orm/Model.d.ts +123 -0
- package/dist/orm/Observer.d.ts +34 -0
- package/dist/orm/QueryBuilder.d.ts +119 -0
- package/dist/orm/Relationship.d.ts +58 -0
- package/dist/orm/index.d.ts +4 -0
- package/dist/orm/index.js +1 -0
- package/dist/pagination/index.d.ts +8 -0
- package/dist/pagination/index.js +0 -0
- package/dist/pdf/GeneratePdfJob.d.ts +99 -0
- package/dist/pdf/GeneratePdfJob.js +41 -0
- package/dist/pdf/index.d.ts +328 -0
- package/dist/pdf/index.js +41 -0
- package/dist/permissions/index.d.ts +161 -0
- package/dist/permissions/index.js +60 -0
- package/dist/plugins/BootstrapPlugins.d.ts +11 -0
- package/dist/plugins/PluginInstaller.d.ts +30 -0
- package/dist/plugins/PluginInstaller.js +1 -0
- package/dist/plugins/PluginPublisher.d.ts +32 -0
- package/dist/plugins/PluginPublisher.js +1 -0
- package/dist/plugins/PluginRegistry.d.ts +55 -0
- package/dist/plugins/PluginRegistry.js +1 -0
- package/dist/plugins/index.d.ts +206 -0
- package/dist/plugins/index.js +1 -0
- package/dist/queue/JobMonitor.d.ts +109 -0
- package/dist/queue/JobMonitor.js +5 -0
- package/dist/queue/index.d.ts +279 -0
- package/dist/queue/index.js +5 -0
- package/dist/repositories/index.d.ts +147 -0
- package/dist/repositories/index.js +1 -0
- package/dist/routing/Controller.d.ts +115 -0
- package/dist/routing/FormRequest.d.ts +94 -0
- package/dist/routing/Resource.d.ts +213 -0
- package/dist/routing/Response.d.ts +138 -0
- package/dist/routing/index.d.ts +4 -0
- package/dist/routing/index.js +5 -0
- package/dist/scheduler/ScheduleMonitor.d.ts +141 -0
- package/dist/scheduler/ScheduleMonitor.js +1 -0
- package/dist/scheduler/SchedulerLock.d.ts +33 -0
- package/dist/scheduler/index.d.ts +208 -0
- package/dist/scheduler/index.js +34 -0
- package/dist/services/index.d.ts +79 -0
- package/dist/services/index.js +1 -0
- package/dist/session/Session.d.ts +166 -0
- package/dist/session/index.d.ts +1 -0
- package/dist/session/index.js +16 -0
- package/dist/storage/index.d.ts +154 -0
- package/dist/storage/index.js +1 -0
- package/dist/support/Pipeline.d.ts +65 -0
- package/dist/support/date.d.ts +136 -0
- package/dist/support/date.js +1 -0
- package/dist/support/index.d.ts +8 -0
- package/dist/support/index.js +1 -0
- package/dist/support/singleton.d.ts +10 -0
- package/dist/support/uuid.d.ts +40 -0
- package/dist/teams/index.d.ts +91 -0
- package/dist/teams/index.js +78 -0
- package/dist/uploads/index.d.ts +63 -0
- package/dist/uploads/index.js +2 -0
- package/dist/validation/index.d.ts +46 -0
- package/dist/validation/index.js +1 -0
- package/dist/webhooks/index.d.ts +66 -0
- package/dist/webhooks/index.js +1 -0
- package/package.json +338 -0
- package/src/i18n/LanguageSwitcher.svelte +47 -0
- package/src/i18n/index.ts +113 -0
- package/src/ui/Alert.svelte +22 -0
- package/src/ui/Avatar.svelte +18 -0
- package/src/ui/AvatarFallback.svelte +18 -0
- package/src/ui/AvatarImage.svelte +12 -0
- package/src/ui/Badge.svelte +27 -0
- package/src/ui/Button.svelte +51 -0
- package/src/ui/Card.svelte +15 -0
- package/src/ui/CardContent.svelte +15 -0
- package/src/ui/CardDescription.svelte +15 -0
- package/src/ui/CardFooter.svelte +15 -0
- package/src/ui/CardHeader.svelte +15 -0
- package/src/ui/CardTitle.svelte +15 -0
- package/src/ui/Icon.svelte +81 -0
- package/src/ui/Input.svelte +40 -0
- package/src/ui/Label.svelte +20 -0
- package/src/ui/Separator.svelte +10 -0
- package/src/ui/Tabs.svelte +23 -0
- package/src/ui/TabsContent.svelte +27 -0
- package/src/ui/TabsList.svelte +19 -0
- package/src/ui/TabsTrigger.svelte +28 -0
- package/src/ui/Toaster.svelte +279 -0
- package/src/ui/index.ts +31 -0
- package/src/ui/toast.ts +212 -0
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Svelar Logger
|
|
3
|
+
*
|
|
4
|
+
* Structured logging with channels (console, file, custom).
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```ts
|
|
8
|
+
* import { Log } from 'svelar/logging';
|
|
9
|
+
*
|
|
10
|
+
* Log.configure({
|
|
11
|
+
* default: 'stack',
|
|
12
|
+
* channels: {
|
|
13
|
+
* console: { driver: 'console', level: 'debug' },
|
|
14
|
+
* file: { driver: 'file', path: 'storage/logs/app.log', level: 'info' },
|
|
15
|
+
* stack: { driver: 'stack', channels: ['console', 'file'] },
|
|
16
|
+
* },
|
|
17
|
+
* });
|
|
18
|
+
*
|
|
19
|
+
* Log.info('User registered', { userId: 1 });
|
|
20
|
+
* Log.error('Payment failed', { orderId: 123, error: 'insufficient_funds' });
|
|
21
|
+
* Log.channel('file').warn('Disk space low');
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
export type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'fatal';
|
|
25
|
+
export type LogDriver = 'console' | 'file' | 'stack' | 'null';
|
|
26
|
+
export interface LogChannelConfig {
|
|
27
|
+
driver: LogDriver;
|
|
28
|
+
level?: LogLevel;
|
|
29
|
+
/** File path for file driver */
|
|
30
|
+
path?: string;
|
|
31
|
+
/** Channel names for stack driver */
|
|
32
|
+
channels?: string[];
|
|
33
|
+
/** Custom formatter */
|
|
34
|
+
format?: 'text' | 'json';
|
|
35
|
+
}
|
|
36
|
+
export interface LogConfig {
|
|
37
|
+
default: string;
|
|
38
|
+
channels: Record<string, LogChannelConfig>;
|
|
39
|
+
}
|
|
40
|
+
interface LogEntry {
|
|
41
|
+
level: LogLevel;
|
|
42
|
+
message: string;
|
|
43
|
+
context: Record<string, any>;
|
|
44
|
+
timestamp: string;
|
|
45
|
+
}
|
|
46
|
+
interface Channel {
|
|
47
|
+
write(entry: LogEntry): void | Promise<void>;
|
|
48
|
+
minLevel: LogLevel;
|
|
49
|
+
}
|
|
50
|
+
declare class LoggerManager {
|
|
51
|
+
private config;
|
|
52
|
+
private channels;
|
|
53
|
+
/**
|
|
54
|
+
* Configure the logger
|
|
55
|
+
*/
|
|
56
|
+
configure(config: LogConfig): void;
|
|
57
|
+
/**
|
|
58
|
+
* Get a specific channel
|
|
59
|
+
*/
|
|
60
|
+
channel(name: string): LoggerFacade;
|
|
61
|
+
debug(message: string, context?: Record<string, any>): void;
|
|
62
|
+
info(message: string, context?: Record<string, any>): void;
|
|
63
|
+
warn(message: string, context?: Record<string, any>): void;
|
|
64
|
+
error(message: string, context?: Record<string, any>): void;
|
|
65
|
+
fatal(message: string, context?: Record<string, any>): void;
|
|
66
|
+
private writeToDefault;
|
|
67
|
+
private resolveChannel;
|
|
68
|
+
private createChannel;
|
|
69
|
+
}
|
|
70
|
+
declare class LoggerFacade {
|
|
71
|
+
private channel;
|
|
72
|
+
constructor(channel: Channel);
|
|
73
|
+
debug(message: string, context?: Record<string, any>): void;
|
|
74
|
+
info(message: string, context?: Record<string, any>): void;
|
|
75
|
+
warn(message: string, context?: Record<string, any>): void;
|
|
76
|
+
error(message: string, context?: Record<string, any>): void;
|
|
77
|
+
fatal(message: string, context?: Record<string, any>): void;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Global Log singleton
|
|
81
|
+
*/
|
|
82
|
+
export declare const Log: LoggerManager;
|
|
83
|
+
export type { Channel, LogEntry };
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import{appendFile as L,mkdir as u}from"fs/promises";import{dirname as w}from"path";function m(r,e){let t=Symbol.for(r),n=globalThis;return n[t]||(n[t]=e()),n[t]}var l={debug:0,info:1,warn:2,error:3,fatal:4},o=class{minLevel;format;constructor(e){this.minLevel=e.level??"debug",this.format=e.format??"text"}write(e){if(l[e.level]<l[this.minLevel])return;if(this.format==="json"){console.log(JSON.stringify(e));return}let t={debug:"\x1B[90m",info:"\x1B[34m",warn:"\x1B[33m",error:"\x1B[31m",fatal:"\x1B[35m"},n="\x1B[0m",s=t[e.level]??"",f=e.level.toUpperCase().padEnd(5),d=Object.keys(e.context).length>0?` ${JSON.stringify(e.context)}`:"",p=e.level==="error"||e.level==="fatal"?"error":"log";console[p](`${s}[${e.timestamp}] ${f}${n} ${e.message}${d}`)}},a=class{minLevel;path;format;initialized=!1;constructor(e){this.minLevel=e.level??"info",this.path=e.path??"storage/logs/app.log",this.format=e.format??"text"}async write(e){if(l[e.level]<l[this.minLevel])return;this.initialized||(await u(w(this.path),{recursive:!0}),this.initialized=!0);let t;if(this.format==="json")t=JSON.stringify(e)+`
|
|
2
|
+
`;else{let n=e.level.toUpperCase().padEnd(5),s=Object.keys(e.context).length>0?` ${JSON.stringify(e.context)}`:"";t=`[${e.timestamp}] ${n} ${e.message}${s}
|
|
3
|
+
`}await L(this.path,t)}},c=class{minLevel="debug";channelNames;resolver;constructor(e,t){this.channelNames=e.channels??[],this.resolver=t,this.minLevel=e.level??"debug"}async write(e){for(let t of this.channelNames){let n=this.resolver(t);n&&await n.write(e)}}},g=class{minLevel="debug";write(){}},h=class{config={default:"console",channels:{console:{driver:"console",level:"debug"}}};channels=new Map;configure(e){this.config=e,this.channels.clear()}channel(e){return new v(this.resolveChannel(e))}debug(e,t={}){this.writeToDefault({level:"debug",message:e,context:t,timestamp:i()})}info(e,t={}){this.writeToDefault({level:"info",message:e,context:t,timestamp:i()})}warn(e,t={}){this.writeToDefault({level:"warn",message:e,context:t,timestamp:i()})}error(e,t={}){this.writeToDefault({level:"error",message:e,context:t,timestamp:i()})}fatal(e,t={}){this.writeToDefault({level:"fatal",message:e,context:t,timestamp:i()})}writeToDefault(e){this.resolveChannel(this.config.default).write(e)}resolveChannel(e){if(this.channels.has(e))return this.channels.get(e);let t=this.config.channels[e];if(!t){let s=new o({driver:"console"});return this.channels.set(e,s),s}let n=this.createChannel(t);return this.channels.set(e,n),n}createChannel(e){switch(e.driver){case"console":return new o(e);case"file":return new a(e);case"stack":return new c(e,t=>this.resolveChannel(t));case"null":return new g;default:return new o(e)}}},v=class{constructor(e){this.channel=e}debug(e,t={}){this.channel.write({level:"debug",message:e,context:t,timestamp:i()})}info(e,t={}){this.channel.write({level:"info",message:e,context:t,timestamp:i()})}warn(e,t={}){this.channel.write({level:"warn",message:e,context:t,timestamp:i()})}error(e,t={}){this.channel.write({level:"error",message:e,context:t,timestamp:i()})}fatal(e,t={}){this.channel.write({level:"fatal",message:e,context:t,timestamp:i()})}};function i(){return new Date().toISOString()}var R=m("svelar.log",()=>new h);export{R as Log};
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Svelar Mail
|
|
3
|
+
*
|
|
4
|
+
* Email sending abstraction with swappable drivers.
|
|
5
|
+
*
|
|
6
|
+
* Drivers:
|
|
7
|
+
* - **smtp** — SMTP via nodemailer (requires `npm install nodemailer`)
|
|
8
|
+
* - **postmark** — Postmark transactional email API (zero deps, uses fetch)
|
|
9
|
+
* - **resend** — Resend email API (zero deps, uses fetch)
|
|
10
|
+
* - **log** — Logs emails to console (development)
|
|
11
|
+
* - **null** — Silently discards emails (testing)
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```ts
|
|
15
|
+
* import { Mailer } from '@beeblock/svelar/mail';
|
|
16
|
+
*
|
|
17
|
+
* Mailer.configure({
|
|
18
|
+
* default: 'resend',
|
|
19
|
+
* mailers: {
|
|
20
|
+
* resend: {
|
|
21
|
+
* driver: 'resend',
|
|
22
|
+
* apiKey: process.env.RESEND_API_KEY,
|
|
23
|
+
* },
|
|
24
|
+
* postmark: {
|
|
25
|
+
* driver: 'postmark',
|
|
26
|
+
* apiToken: process.env.POSTMARK_API_TOKEN,
|
|
27
|
+
* },
|
|
28
|
+
* smtp: {
|
|
29
|
+
* driver: 'smtp',
|
|
30
|
+
* host: 'smtp.example.com',
|
|
31
|
+
* port: 587,
|
|
32
|
+
* auth: { user: 'you@example.com', pass: 'secret' },
|
|
33
|
+
* },
|
|
34
|
+
* log: { driver: 'log' },
|
|
35
|
+
* },
|
|
36
|
+
* from: { name: 'My App', address: 'noreply@example.com' },
|
|
37
|
+
* });
|
|
38
|
+
*
|
|
39
|
+
* // Send (uses default driver)
|
|
40
|
+
* await Mailer.send({
|
|
41
|
+
* to: 'user@example.com',
|
|
42
|
+
* subject: 'Welcome!',
|
|
43
|
+
* html: '<h1>Welcome to our app!</h1>',
|
|
44
|
+
* });
|
|
45
|
+
*
|
|
46
|
+
* // Send via a specific driver
|
|
47
|
+
* await Mailer.mailer('postmark').send({ ... });
|
|
48
|
+
*
|
|
49
|
+
* // Mailable class
|
|
50
|
+
* class WelcomeEmail extends Mailable {
|
|
51
|
+
* constructor(private user: User) { super(); }
|
|
52
|
+
* build() {
|
|
53
|
+
* return this
|
|
54
|
+
* .to(this.user.email)
|
|
55
|
+
* .subject('Welcome!')
|
|
56
|
+
* .html(`<h1>Hi ${this.user.name}!</h1>`);
|
|
57
|
+
* }
|
|
58
|
+
* }
|
|
59
|
+
*
|
|
60
|
+
* await Mailer.sendMailable(new WelcomeEmail(user));
|
|
61
|
+
* ```
|
|
62
|
+
*/
|
|
63
|
+
export type MailDriver = 'smtp' | 'postmark' | 'resend' | 'log' | 'null' | 'custom';
|
|
64
|
+
export interface MailerConfig {
|
|
65
|
+
default: string;
|
|
66
|
+
mailers: Record<string, MailDriverConfig>;
|
|
67
|
+
from?: {
|
|
68
|
+
name: string;
|
|
69
|
+
address: string;
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
export interface MailDriverConfig {
|
|
73
|
+
driver: MailDriver;
|
|
74
|
+
host?: string;
|
|
75
|
+
port?: number;
|
|
76
|
+
secure?: boolean;
|
|
77
|
+
auth?: {
|
|
78
|
+
user: string;
|
|
79
|
+
pass: string;
|
|
80
|
+
};
|
|
81
|
+
apiToken?: string;
|
|
82
|
+
/** Postmark message stream (default: 'outbound') */
|
|
83
|
+
messageStream?: string;
|
|
84
|
+
apiKey?: string;
|
|
85
|
+
transport?: MailTransport;
|
|
86
|
+
}
|
|
87
|
+
export interface MailMessage {
|
|
88
|
+
to: string | string[];
|
|
89
|
+
cc?: string | string[];
|
|
90
|
+
bcc?: string | string[];
|
|
91
|
+
from?: string | {
|
|
92
|
+
name: string;
|
|
93
|
+
address: string;
|
|
94
|
+
};
|
|
95
|
+
replyTo?: string;
|
|
96
|
+
subject: string;
|
|
97
|
+
text?: string;
|
|
98
|
+
html?: string;
|
|
99
|
+
attachments?: Array<{
|
|
100
|
+
filename: string;
|
|
101
|
+
content: string | Buffer;
|
|
102
|
+
contentType?: string;
|
|
103
|
+
}>;
|
|
104
|
+
/** Optional tags for analytics (supported by Postmark and Resend) */
|
|
105
|
+
tags?: Record<string, string>;
|
|
106
|
+
}
|
|
107
|
+
export interface SendResult {
|
|
108
|
+
accepted: string[];
|
|
109
|
+
rejected: string[];
|
|
110
|
+
messageId?: string;
|
|
111
|
+
}
|
|
112
|
+
interface MailTransport {
|
|
113
|
+
send(message: MailMessage): Promise<SendResult>;
|
|
114
|
+
}
|
|
115
|
+
export declare abstract class Mailable {
|
|
116
|
+
private message;
|
|
117
|
+
abstract build(): this;
|
|
118
|
+
to(address: string | string[]): this;
|
|
119
|
+
cc(address: string | string[]): this;
|
|
120
|
+
bcc(address: string | string[]): this;
|
|
121
|
+
from(address: string | {
|
|
122
|
+
name: string;
|
|
123
|
+
address: string;
|
|
124
|
+
}): this;
|
|
125
|
+
replyTo(address: string): this;
|
|
126
|
+
subject(subject: string): this;
|
|
127
|
+
text(content: string): this;
|
|
128
|
+
html(content: string): this;
|
|
129
|
+
attach(filename: string, content: string | Buffer, contentType?: string): this;
|
|
130
|
+
tag(name: string, value: string): this;
|
|
131
|
+
/** @internal */
|
|
132
|
+
toMessage(): MailMessage;
|
|
133
|
+
}
|
|
134
|
+
declare class MailManager {
|
|
135
|
+
private config;
|
|
136
|
+
private transports;
|
|
137
|
+
configure(config: MailerConfig): void;
|
|
138
|
+
send(message: MailMessage, mailer?: string): Promise<SendResult>;
|
|
139
|
+
sendMailable(mailable: Mailable, mailer?: string): Promise<SendResult>;
|
|
140
|
+
mailer(name: string): {
|
|
141
|
+
send: (msg: MailMessage) => Promise<SendResult>;
|
|
142
|
+
};
|
|
143
|
+
private resolveTransport;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Global Mailer singleton
|
|
147
|
+
*/
|
|
148
|
+
export declare const Mailer: MailManager;
|
|
149
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
function M(s,t){let r=Symbol.for(s),e=globalThis;return e[r]||(e[r]=t()),e[r]}function y(s){return typeof s=="string"?s:`${s.name} <${s.address}>`}function c(s){return s?Array.isArray(s)?s:[s]:[]}function b(s){return Buffer.isBuffer(s)?s.toString("base64"):Buffer.from(s).toString("base64")}var f=class{async send(t){let r=c(t.to);return console.log(`[Mail] To: ${r.join(", ")} | Subject: ${t.subject}`),t.text&&console.log(`[Mail] Body: ${t.text.slice(0,200)}`),{accepted:r,rejected:[]}}},h=class{async send(t){return{accepted:c(t.to),rejected:[]}}},p=class{constructor(t){this.config=t}async send(t){try{let i=await(await import("nodemailer")).createTransport({host:this.config.host,port:this.config.port??587,secure:this.config.secure??!1,auth:this.config.auth}).sendMail({from:t.from?y(t.from):void 0,to:c(t.to).join(", "),cc:c(t.cc).join(", ")||void 0,bcc:c(t.bcc).join(", ")||void 0,replyTo:t.replyTo,subject:t.subject,text:t.text,html:t.html,attachments:t.attachments});return{accepted:i.accepted,rejected:i.rejected,messageId:i.messageId}}catch(r){throw r.code==="MODULE_NOT_FOUND"?new Error("SMTP driver requires nodemailer. Install: npm install nodemailer"):r}}},d=class{constructor(t){this.config=t}async send(t){let r=this.config.apiToken;if(!r)throw new Error("Postmark apiToken is required. Set it in your mailer config or POSTMARK_API_TOKEN env var.");let e=c(t.to),i=c(t.cc),l=c(t.bcc),n={From:t.from?y(t.from):void 0,To:e.join(", "),Subject:t.subject,MessageStream:this.config.messageStream||"outbound"};i.length>0&&(n.Cc=i.join(", ")),l.length>0&&(n.Bcc=l.join(", ")),t.replyTo&&(n.ReplyTo=t.replyTo),t.html&&(n.HtmlBody=t.html),t.text&&(n.TextBody=t.text),!n.HtmlBody&&!n.TextBody&&(n.TextBody=""),t.tags&&(n.Tag=Object.values(t.tags)[0]),t.attachments?.length&&(n.Attachments=t.attachments.map(o=>({Name:o.filename,Content:b(o.content),ContentType:o.contentType||"application/octet-stream"})));let a=await fetch("https://api.postmarkapp.com/email",{method:"POST",headers:{Accept:"application/json","Content-Type":"application/json","X-Postmark-Server-Token":r},body:JSON.stringify(n)});if(!a.ok){let o=await a.json().catch(()=>({Message:a.statusText}));throw new Error(`Postmark error ${a.status}: ${o.Message||JSON.stringify(o)}`)}let g=await a.json();return{accepted:e,rejected:[],messageId:g.MessageID}}},u=class{constructor(t){this.config=t}async send(t){let r=this.config.apiKey;if(!r)throw new Error("Resend apiKey is required. Set it in your mailer config or RESEND_API_KEY env var.");let e=c(t.to),i=c(t.cc),l=c(t.bcc),n={from:t.from?y(t.from):void 0,to:e,subject:t.subject};i.length>0&&(n.cc=i),l.length>0&&(n.bcc=l),t.replyTo&&(n.reply_to=[t.replyTo]),t.html&&(n.html=t.html),t.text&&(n.text=t.text),t.tags&&(n.tags=Object.entries(t.tags).map(([o,w])=>({name:o,value:w}))),t.attachments?.length&&(n.attachments=t.attachments.map(o=>({filename:o.filename,content:b(o.content),content_type:o.contentType||"application/octet-stream"})));let a=await fetch("https://api.resend.com/emails",{method:"POST",headers:{Authorization:`Bearer ${r}`,"Content-Type":"application/json"},body:JSON.stringify(n)});if(!a.ok){let o=await a.json().catch(()=>({message:a.statusText}));throw new Error(`Resend error ${a.status}: ${o.message||JSON.stringify(o)}`)}let g=await a.json();return{accepted:e,rejected:[],messageId:g.id}}},T=class{message={};to(t){return this.message.to=t,this}cc(t){return this.message.cc=t,this}bcc(t){return this.message.bcc=t,this}from(t){return this.message.from=t,this}replyTo(t){return this.message.replyTo=t,this}subject(t){return this.message.subject=t,this}text(t){return this.message.text=t,this}html(t){return this.message.html=t,this}attach(t,r,e){return this.message.attachments||(this.message.attachments=[]),this.message.attachments.push({filename:t,content:r,contentType:e}),this}tag(t,r){return this.message.tags||(this.message.tags={}),this.message.tags[t]=r,this}toMessage(){return this.message}},m=class{config=null;transports=new Map;configure(t){this.config=t,this.transports.clear()}async send(t,r){let e=this.resolveTransport(r);return!t.from&&this.config?.from&&(t.from=this.config.from),e.send(t)}async sendMailable(t,r){t.build();let e=t.toMessage();return!e.from&&this.config?.from&&(e.from=this.config.from),this.send(e,r)}mailer(t){let r=this.resolveTransport(t);return{send:e=>(!e.from&&this.config?.from&&(e.from=this.config.from),r.send(e))}}resolveTransport(t){let r=t??this.config?.default??"log";if(this.transports.has(r))return this.transports.get(r);if(!this.config){let l=new f;return this.transports.set(r,l),l}let e=this.config.mailers[r];if(!e)throw new Error(`Mailer "${r}" is not defined.`);let i;switch(e.driver){case"smtp":i=new p(e);break;case"postmark":i=new d(e);break;case"resend":i=new u(e);break;case"log":i=new f;break;case"null":i=new h;break;case"custom":{if(!e.transport)throw new Error(`Custom mail driver "${r}" requires a "transport" instance.`);i=e.transport;break}default:throw new Error(`Unknown mail driver: ${e.driver}`)}return this.transports.set(r,i),i}},S=M("svelar.mail",()=>new m);export{T as Mailable,S as Mailer};
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Svelar Middleware
|
|
3
|
+
*
|
|
4
|
+
* Laravel-inspired middleware system that integrates with SvelteKit hooks.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```ts
|
|
8
|
+
* class AuthMiddleware extends Middleware {
|
|
9
|
+
* async handle(ctx: MiddlewareContext, next: NextFunction): Promise<Response | void> {
|
|
10
|
+
* const token = ctx.event.request.headers.get('authorization');
|
|
11
|
+
* if (!token) return new Response('Unauthorized', { status: 401 });
|
|
12
|
+
* ctx.event.locals.user = await verifyToken(token);
|
|
13
|
+
* return next();
|
|
14
|
+
* }
|
|
15
|
+
* }
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
export interface MiddlewareContext {
|
|
19
|
+
/** The SvelteKit RequestEvent */
|
|
20
|
+
event: any;
|
|
21
|
+
/** Route parameters */
|
|
22
|
+
params: Record<string, any>;
|
|
23
|
+
/** App.Locals */
|
|
24
|
+
locals: Record<string, any>;
|
|
25
|
+
}
|
|
26
|
+
export type NextFunction = () => Promise<Response | void>;
|
|
27
|
+
export type MiddlewareHandler = (ctx: MiddlewareContext, next: NextFunction) => Promise<Response | void>;
|
|
28
|
+
export declare abstract class Middleware {
|
|
29
|
+
/**
|
|
30
|
+
* Handle the incoming request.
|
|
31
|
+
* Call next() to pass the request to the next middleware.
|
|
32
|
+
* Return a Response to short-circuit the pipeline.
|
|
33
|
+
*/
|
|
34
|
+
abstract handle(ctx: MiddlewareContext, next: NextFunction): Promise<Response | void>;
|
|
35
|
+
}
|
|
36
|
+
export declare class MiddlewareStack {
|
|
37
|
+
private middleware;
|
|
38
|
+
private namedMiddleware;
|
|
39
|
+
/**
|
|
40
|
+
* Add middleware to the stack
|
|
41
|
+
*/
|
|
42
|
+
use(middleware: Middleware | MiddlewareHandler | (new () => Middleware)): this;
|
|
43
|
+
/**
|
|
44
|
+
* Register named middleware (for per-route usage)
|
|
45
|
+
*/
|
|
46
|
+
register(name: string, middleware: Middleware | MiddlewareHandler | (new () => Middleware)): this;
|
|
47
|
+
/**
|
|
48
|
+
* Get a named middleware
|
|
49
|
+
*/
|
|
50
|
+
get(name: string): Middleware | MiddlewareHandler | undefined;
|
|
51
|
+
/**
|
|
52
|
+
* Execute the middleware stack
|
|
53
|
+
*/
|
|
54
|
+
execute(ctx: MiddlewareContext, finalHandler: () => Promise<Response | void>, additionalMiddleware?: string[]): Promise<Response | void>;
|
|
55
|
+
/**
|
|
56
|
+
* Get the count of global middleware
|
|
57
|
+
*/
|
|
58
|
+
count(): number;
|
|
59
|
+
}
|
|
60
|
+
/** CORS Middleware */
|
|
61
|
+
export declare class CorsMiddleware extends Middleware {
|
|
62
|
+
private options;
|
|
63
|
+
constructor(options?: {
|
|
64
|
+
origin?: string | string[];
|
|
65
|
+
methods?: string[];
|
|
66
|
+
headers?: string[];
|
|
67
|
+
credentials?: boolean;
|
|
68
|
+
maxAge?: number;
|
|
69
|
+
});
|
|
70
|
+
handle(ctx: MiddlewareContext, next: NextFunction): Promise<Response | void>;
|
|
71
|
+
}
|
|
72
|
+
/** Rate Limiting Middleware */
|
|
73
|
+
export declare class RateLimitMiddleware extends Middleware {
|
|
74
|
+
private requests;
|
|
75
|
+
private maxRequests;
|
|
76
|
+
private windowMs;
|
|
77
|
+
constructor(options?: {
|
|
78
|
+
maxRequests?: number;
|
|
79
|
+
windowMs?: number;
|
|
80
|
+
});
|
|
81
|
+
handle(ctx: MiddlewareContext, next: NextFunction): Promise<Response | void>;
|
|
82
|
+
}
|
|
83
|
+
/** Logging Middleware */
|
|
84
|
+
export declare class LoggingMiddleware extends Middleware {
|
|
85
|
+
handle(ctx: MiddlewareContext, next: NextFunction): Promise<Response | void>;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* CSRF Protection Middleware (Double-submit cookie pattern)
|
|
89
|
+
*
|
|
90
|
+
* Generates a random CSRF token, sets it as a cookie, and validates
|
|
91
|
+
* that mutation requests (POST, PUT, PATCH, DELETE) include the token
|
|
92
|
+
* in either the `X-CSRF-Token` header or a `_csrf` body field.
|
|
93
|
+
*
|
|
94
|
+
* Safe methods (GET, HEAD, OPTIONS) and API requests with Bearer tokens
|
|
95
|
+
* are exempt since they are not vulnerable to CSRF.
|
|
96
|
+
*/
|
|
97
|
+
export declare class CsrfMiddleware extends Middleware {
|
|
98
|
+
private cookieName;
|
|
99
|
+
private headerName;
|
|
100
|
+
private fieldName;
|
|
101
|
+
private excludePaths;
|
|
102
|
+
private onlyPaths;
|
|
103
|
+
constructor(options?: {
|
|
104
|
+
cookieName?: string;
|
|
105
|
+
headerName?: string;
|
|
106
|
+
fieldName?: string;
|
|
107
|
+
excludePaths?: string[];
|
|
108
|
+
/** If set, CSRF validation only applies to requests matching these path prefixes */
|
|
109
|
+
onlyPaths?: string[];
|
|
110
|
+
});
|
|
111
|
+
handle(ctx: MiddlewareContext, next: NextFunction): Promise<Response | void>;
|
|
112
|
+
private setTokenAndContinue;
|
|
113
|
+
private getCookieToken;
|
|
114
|
+
private getBodyToken;
|
|
115
|
+
private generateToken;
|
|
116
|
+
private timingSafeEqual;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Origin Validation Middleware
|
|
120
|
+
*
|
|
121
|
+
* Blocks mutation requests (POST, PUT, PATCH, DELETE) where the Origin header
|
|
122
|
+
* doesn't match the application's own origin. Prevents cross-origin API abuse.
|
|
123
|
+
*
|
|
124
|
+
* SvelteKit's own CSRF check validates Origin on form submissions, but this
|
|
125
|
+
* extends the protection to fetch/XHR API calls as well.
|
|
126
|
+
*/
|
|
127
|
+
export declare class OriginMiddleware extends Middleware {
|
|
128
|
+
private allowedOrigins;
|
|
129
|
+
constructor(options?: {
|
|
130
|
+
/** Extra allowed origins beyond the app's own (e.g. for mobile apps or partner APIs) */
|
|
131
|
+
allowedOrigins?: string[];
|
|
132
|
+
});
|
|
133
|
+
handle(ctx: MiddlewareContext, next: NextFunction): Promise<Response | void>;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* API Signature Verification Middleware
|
|
137
|
+
*
|
|
138
|
+
* Verifies that incoming API requests are signed with an HMAC signature.
|
|
139
|
+
* This provides an extra layer of security beyond Bearer tokens — even if
|
|
140
|
+
* a token is stolen, requests can't be forged without the signing secret.
|
|
141
|
+
*
|
|
142
|
+
* Clients sign requests by computing:
|
|
143
|
+
* HMAC-SHA256(secret, timestamp + method + path + body)
|
|
144
|
+
*
|
|
145
|
+
* And sending the signature + timestamp in headers:
|
|
146
|
+
* X-Signature: <hex digest>
|
|
147
|
+
* X-Timestamp: <unix seconds>
|
|
148
|
+
*
|
|
149
|
+
* The middleware rejects requests with:
|
|
150
|
+
* - Missing signature or timestamp headers
|
|
151
|
+
* - Timestamp older than the tolerance window (prevents replay attacks)
|
|
152
|
+
* - Invalid HMAC signature
|
|
153
|
+
*
|
|
154
|
+
* @example
|
|
155
|
+
* ```ts
|
|
156
|
+
* new SignatureMiddleware({
|
|
157
|
+
* secret: process.env.API_SIGNING_SECRET,
|
|
158
|
+
* tolerance: 300, // 5 minutes
|
|
159
|
+
* })
|
|
160
|
+
* ```
|
|
161
|
+
*/
|
|
162
|
+
export declare class SignatureMiddleware extends Middleware {
|
|
163
|
+
private secret;
|
|
164
|
+
private tolerance;
|
|
165
|
+
private signatureHeader;
|
|
166
|
+
private timestampHeader;
|
|
167
|
+
private onlyPaths;
|
|
168
|
+
constructor(options: {
|
|
169
|
+
/** The shared signing secret */
|
|
170
|
+
secret: string;
|
|
171
|
+
/** Max age of a request in seconds (default: 300 = 5 minutes) */
|
|
172
|
+
tolerance?: number;
|
|
173
|
+
/** Header name for the signature (default: 'X-Signature') */
|
|
174
|
+
signatureHeader?: string;
|
|
175
|
+
/** Header name for the timestamp (default: 'X-Timestamp') */
|
|
176
|
+
timestampHeader?: string;
|
|
177
|
+
/** If set, only enforce signature on these path prefixes */
|
|
178
|
+
onlyPaths?: string[];
|
|
179
|
+
});
|
|
180
|
+
handle(ctx: MiddlewareContext, next: NextFunction): Promise<Response | void>;
|
|
181
|
+
private timingSafeCompare;
|
|
182
|
+
/**
|
|
183
|
+
* Helper to generate a signature on the client side.
|
|
184
|
+
* Can be used in Node.js clients or exported for documentation.
|
|
185
|
+
*/
|
|
186
|
+
static sign(secret: string, method: string, path: string, body: string, timestamp?: number): {
|
|
187
|
+
signature: string;
|
|
188
|
+
timestamp: number;
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Throttle Middleware — stricter per-route rate limiting
|
|
193
|
+
*
|
|
194
|
+
* Unlike the global RateLimitMiddleware, this is designed for specific
|
|
195
|
+
* sensitive routes (login, register, password reset) with lower thresholds.
|
|
196
|
+
*/
|
|
197
|
+
export declare class ThrottleMiddleware extends Middleware {
|
|
198
|
+
private attempts;
|
|
199
|
+
private maxAttempts;
|
|
200
|
+
private decayMinutes;
|
|
201
|
+
constructor(options?: {
|
|
202
|
+
/** Max attempts before blocking (default: 5) */
|
|
203
|
+
maxAttempts?: number;
|
|
204
|
+
/** Decay period in minutes (default: 1) */
|
|
205
|
+
decayMinutes?: number;
|
|
206
|
+
});
|
|
207
|
+
handle(ctx: MiddlewareContext, next: NextFunction): Promise<Response | void>;
|
|
208
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { Middleware, MiddlewareStack, CorsMiddleware, RateLimitMiddleware, LoggingMiddleware, CsrfMiddleware, OriginMiddleware, ThrottleMiddleware, SignatureMiddleware, type MiddlewareContext, type NextFunction, type MiddlewareHandler, } from './Middleware.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var R=(d=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(d,{get:(e,t)=>(typeof require<"u"?require:e)[t]}):d)(function(d){if(typeof require<"u")return require.apply(this,arguments);throw Error('Dynamic require of "'+d+'" is not supported')});var l=class{},h=class{middleware=[];namedMiddleware=new Map;use(e){return typeof e=="function"&&"prototype"in e&&typeof e.prototype?.handle=="function"?this.middleware.push(new e):this.middleware.push(e),this}register(e,t){return typeof t=="function"&&"prototype"in t&&typeof t.prototype?.handle=="function"?this.namedMiddleware.set(e,new t):this.namedMiddleware.set(e,t),this}get(e){return this.namedMiddleware.get(e)}async execute(e,t,s){let n=[...this.middleware];if(s)for(let r of s){let a=this.namedMiddleware.get(r);a&&n.push(a)}let i=t;for(let r=n.length-1;r>=0;r--){let a=n[r],o=i;typeof a.handle=="function"?i=()=>a.handle(e,o):i=()=>a(e,o)}return i()}count(){return this.middleware.length}},p=class extends l{constructor(t={}){super();this.options=t}async handle(t,s){let n=await s();if(!n)return;let i=Array.isArray(this.options.origin)?this.options.origin.join(", "):this.options.origin??"*";return n.headers.set("Access-Control-Allow-Origin",i),n.headers.set("Access-Control-Allow-Methods",(this.options.methods??["GET","POST","PUT","DELETE","PATCH","OPTIONS"]).join(", ")),n.headers.set("Access-Control-Allow-Headers",(this.options.headers??["Content-Type","Authorization"]).join(", ")),this.options.credentials&&n.headers.set("Access-Control-Allow-Credentials","true"),this.options.maxAge&&n.headers.set("Access-Control-Max-Age",String(this.options.maxAge)),t.event.request.method==="OPTIONS"?new Response(null,{status:204,headers:n.headers}):n}},m=class extends l{requests=new Map;maxRequests;windowMs;constructor(e={}){super(),this.maxRequests=e.maxRequests??60,this.windowMs=e.windowMs??6e4}async handle(e,t){let s=e.event.request.headers.get("x-forwarded-for")??e.event.getClientAddress?.()??"unknown",n=Date.now(),i=this.requests.get(s);if(i&&n<i.resetAt){if(i.count>=this.maxRequests)return new Response(JSON.stringify({error:"Too many requests"}),{status:429,headers:{"Content-Type":"application/json","Retry-After":String(Math.ceil((i.resetAt-n)/1e3))}});i.count++}else this.requests.set(s,{count:1,resetAt:n+this.windowMs});return t()}},g=class extends l{async handle(e,t){let s=Date.now(),n=e.event.request.method,i=e.event.url.pathname,r=await t(),a=Date.now()-s,o=r instanceof Response?r.status:200;return console.log(`[${new Date().toISOString()}] ${n} ${i} \u2192 ${o} (${a}ms)`),r}},w=class extends l{cookieName;headerName;fieldName;excludePaths;onlyPaths;constructor(e={}){super(),this.cookieName=e.cookieName??"XSRF-TOKEN",this.headerName=e.headerName??"X-CSRF-Token",this.fieldName=e.fieldName??"_csrf",this.excludePaths=e.excludePaths??[],this.onlyPaths=e.onlyPaths??null}async handle(e,t){let{event:s}=e,n=s.request.method.toUpperCase();if(["GET","HEAD","OPTIONS"].includes(n))return this.setTokenAndContinue(e,t);if((s.request.headers.get("authorization")??"").startsWith("Bearer "))return t();let r=s.url.pathname;if(this.onlyPaths&&!this.onlyPaths.some(c=>r.startsWith(c)))return this.setTokenAndContinue(e,t);if(this.excludePaths.some(c=>r.startsWith(c)))return t();let a=this.getCookieToken(s),o=s.request.headers.get(this.headerName)??await this.getBodyToken(s);return!a||!o||!this.timingSafeEqual(a,o)?new Response(JSON.stringify({message:"CSRF token mismatch"}),{status:419,headers:{"Content-Type":"application/json"}}):t()}async setTokenAndContinue(e,t){let s=await t();if(!(s instanceof Response))return s;let i=this.getCookieToken(e.event)||this.generateToken();return s.headers.append("Set-Cookie",`${this.cookieName}=${i}; Path=/; SameSite=Lax`),s}getCookieToken(e){let s=(e.request.headers.get("cookie")??"").match(new RegExp(`${this.cookieName}=([^;]+)`));return s?s[1]:null}async getBodyToken(e){try{let t=e.request.headers.get("content-type")??"";if(t.includes("application/x-www-form-urlencoded")||t.includes("multipart/form-data"))return(await e.request.clone().formData()).get(this.fieldName);if(t.includes("application/json"))return(await e.request.clone().json())[this.fieldName]??null}catch{}return null}generateToken(){let e=new Uint8Array(32);return crypto.getRandomValues(e),Array.from(e,t=>t.toString(16).padStart(2,"0")).join("")}timingSafeEqual(e,t){if(e.length!==t.length)return!1;let s=new TextEncoder,n=s.encode(e),i=s.encode(t),r=0;for(let a=0;a<n.length;a++)r|=n[a]^i[a];return r===0}},f=class extends l{allowedOrigins;constructor(e={}){super(),this.allowedOrigins=new Set(e.allowedOrigins??[])}async handle(e,t){let{event:s}=e,n=s.request.method.toUpperCase();if(["GET","HEAD","OPTIONS"].includes(n)||(s.request.headers.get("authorization")??"").startsWith("Bearer "))return t();let r=s.request.headers.get("origin");if(!r)return t();let a=s.url.origin;return r===a||this.allowedOrigins.has(r)?t():new Response(JSON.stringify({message:"Cross-origin request blocked"}),{status:403,headers:{"Content-Type":"application/json"}})}},y=class extends l{secret;tolerance;signatureHeader;timestampHeader;onlyPaths;constructor(e){super(),this.secret=e.secret,this.tolerance=e.tolerance??300,this.signatureHeader=e.signatureHeader??"X-Signature",this.timestampHeader=e.timestampHeader??"X-Timestamp",this.onlyPaths=e.onlyPaths??null}async handle(e,t){let{event:s}=e;if(this.onlyPaths){let A=s.url.pathname;if(!this.onlyPaths.some(P=>A.startsWith(P)))return t()}let n=s.request.headers.get(this.signatureHeader),i=s.request.headers.get(this.timestampHeader);if(!n||!i)return new Response(JSON.stringify({message:"Missing request signature"}),{status:401,headers:{"Content-Type":"application/json"}});let r=parseInt(i,10),a=Math.floor(Date.now()/1e3);if(isNaN(r)||Math.abs(a-r)>this.tolerance)return new Response(JSON.stringify({message:"Request signature expired"}),{status:401,headers:{"Content-Type":"application/json"}});let c=await s.request.clone().text(),{createHmac:u}=await import("crypto"),v=s.request.method.toUpperCase(),C=s.url.pathname+s.url.search,N=`${i}.${v}.${C}.${c}`,M=u("sha256",this.secret).update(N).digest("hex");return n.length!==M.length||!this.timingSafeCompare(n,M)?new Response(JSON.stringify({message:"Invalid request signature"}),{status:401,headers:{"Content-Type":"application/json"}}):t()}timingSafeCompare(e,t){if(e.length!==t.length)return!1;let s=new TextEncoder,n=s.encode(e),i=s.encode(t),r=0;for(let a=0;a<n.length;a++)r|=n[a]^i[a];return r===0}static sign(e,t,s,n,i){let{createHmac:r}=R("crypto"),a=i??Math.floor(Date.now()/1e3),o=`${a}.${t.toUpperCase()}.${s}.${n}`;return{signature:r("sha256",e).update(o).digest("hex"),timestamp:a}}},x=class extends l{attempts=new Map;maxAttempts;decayMinutes;constructor(e={}){super(),this.maxAttempts=e.maxAttempts??5,this.decayMinutes=e.decayMinutes??1}async handle(e,t){let n=`${e.event.request.headers.get("x-forwarded-for")??e.event.getClientAddress?.()??"unknown"}:${e.event.url.pathname}`,i=Date.now(),r=this.attempts.get(n);if(r){if(i<r.blockedUntil){let o=Math.ceil((r.blockedUntil-i)/1e3);return new Response(JSON.stringify({message:"Too many attempts. Please try again later.",retry_after:o}),{status:429,headers:{"Content-Type":"application/json","Retry-After":String(o)}})}if(r.count>=this.maxAttempts){r.blockedUntil=i+this.decayMinutes*6e4,r.count=0;let o=this.decayMinutes*60;return new Response(JSON.stringify({message:"Too many attempts. Please try again later.",retry_after:o}),{status:429,headers:{"Content-Type":"application/json","Retry-After":String(o)}})}}let a=await t();if(a instanceof Response&&a.status>=400&&a.status<500){let o=this.attempts.get(n)??{count:0,blockedUntil:0};if(o.count++,this.attempts.set(n,o),this.attempts.size>1e4)for(let[c,u]of this.attempts)i>u.blockedUntil+this.decayMinutes*6e4*2&&this.attempts.delete(c)}return a}};export{p as CorsMiddleware,w as CsrfMiddleware,g as LoggingMiddleware,l as Middleware,h as MiddlewareStack,f as OriginMiddleware,m as RateLimitMiddleware,y as SignatureMiddleware,x as ThrottleMiddleware};
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Svelar Notifications
|
|
3
|
+
*
|
|
4
|
+
* Multi-channel notification system (mail, database, custom channels).
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```ts
|
|
8
|
+
* import { Notification, Notifier } from 'svelar/notifications';
|
|
9
|
+
*
|
|
10
|
+
* class InvoicePaid extends Notification {
|
|
11
|
+
* constructor(private invoice: Invoice) { super(); }
|
|
12
|
+
*
|
|
13
|
+
* via() { return ['mail', 'database']; }
|
|
14
|
+
*
|
|
15
|
+
* toMail() {
|
|
16
|
+
* return {
|
|
17
|
+
* subject: 'Invoice Paid',
|
|
18
|
+
* html: `<p>Invoice #${this.invoice.id} has been paid.</p>`,
|
|
19
|
+
* };
|
|
20
|
+
* }
|
|
21
|
+
*
|
|
22
|
+
* toDatabase() {
|
|
23
|
+
* return {
|
|
24
|
+
* type: 'invoice_paid',
|
|
25
|
+
* data: { invoiceId: this.invoice.id, amount: this.invoice.amount },
|
|
26
|
+
* };
|
|
27
|
+
* }
|
|
28
|
+
* }
|
|
29
|
+
*
|
|
30
|
+
* await Notifier.send(user, new InvoicePaid(invoice));
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
export type NotificationChannel = 'mail' | 'database' | string;
|
|
34
|
+
export interface NotificationMailData {
|
|
35
|
+
subject: string;
|
|
36
|
+
html?: string;
|
|
37
|
+
text?: string;
|
|
38
|
+
from?: string | {
|
|
39
|
+
name: string;
|
|
40
|
+
address: string;
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
export interface NotificationDatabaseData {
|
|
44
|
+
type: string;
|
|
45
|
+
data: Record<string, any>;
|
|
46
|
+
}
|
|
47
|
+
export interface Notifiable {
|
|
48
|
+
getAttribute(key: string): any;
|
|
49
|
+
/** Email address for mail notifications */
|
|
50
|
+
routeNotificationForMail?(): string;
|
|
51
|
+
}
|
|
52
|
+
export declare abstract class Notification {
|
|
53
|
+
/** Channels to deliver this notification on */
|
|
54
|
+
abstract via(notifiable: Notifiable): NotificationChannel[];
|
|
55
|
+
/** Format for mail channel */
|
|
56
|
+
toMail?(notifiable: Notifiable): NotificationMailData;
|
|
57
|
+
/** Format for database channel */
|
|
58
|
+
toDatabase?(notifiable: Notifiable): NotificationDatabaseData;
|
|
59
|
+
/** Custom channel format (override for custom channels) */
|
|
60
|
+
toChannel?(channel: string, notifiable: Notifiable): any;
|
|
61
|
+
}
|
|
62
|
+
interface NotificationChannelDriver {
|
|
63
|
+
send(notifiable: Notifiable, notification: Notification): Promise<void>;
|
|
64
|
+
}
|
|
65
|
+
declare class NotifierManager {
|
|
66
|
+
private channels;
|
|
67
|
+
constructor();
|
|
68
|
+
/**
|
|
69
|
+
* Register a custom notification channel
|
|
70
|
+
*/
|
|
71
|
+
extend(name: string, channel: NotificationChannelDriver): void;
|
|
72
|
+
/**
|
|
73
|
+
* Send a notification to a notifiable entity
|
|
74
|
+
*/
|
|
75
|
+
send(notifiable: Notifiable | Notifiable[], notification: Notification): Promise<void>;
|
|
76
|
+
/**
|
|
77
|
+
* Send a notification on specific channels only
|
|
78
|
+
*/
|
|
79
|
+
sendVia(notifiable: Notifiable, notification: Notification, channels: NotificationChannel[]): Promise<void>;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Global Notifier singleton
|
|
83
|
+
*/
|
|
84
|
+
export declare const Notifier: NotifierManager;
|
|
85
|
+
export {};
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
var R=Object.defineProperty;var u=(a,t)=>()=>(a&&(t=a(a=0)),t);var x=(a,t)=>{for(var e in t)R(a,e,{get:t[e],enumerable:!0})};function h(a,t){let e=Symbol.for(a),r=globalThis;return r[e]||(r[e]=t()),r[e]}var g=u(()=>{"use strict"});var j={};x(j,{Mailable:()=>C,Mailer:()=>k});function M(a){return typeof a=="string"?a:`${a.name} <${a.address}>`}function d(a){return a?Array.isArray(a)?a:[a]:[]}function P(a){return Buffer.isBuffer(a)?a.toString("base64"):Buffer.from(a).toString("base64")}var p,m,w,y,b,C,v,k,A=u(()=>{"use strict";g();p=class{async send(t){let e=d(t.to);return console.log(`[Mail] To: ${e.join(", ")} | Subject: ${t.subject}`),t.text&&console.log(`[Mail] Body: ${t.text.slice(0,200)}`),{accepted:e,rejected:[]}}},m=class{async send(t){return{accepted:d(t.to),rejected:[]}}},w=class{constructor(t){this.config=t}async send(t){try{let n=await(await import("nodemailer")).createTransport({host:this.config.host,port:this.config.port??587,secure:this.config.secure??!1,auth:this.config.auth}).sendMail({from:t.from?M(t.from):void 0,to:d(t.to).join(", "),cc:d(t.cc).join(", ")||void 0,bcc:d(t.bcc).join(", ")||void 0,replyTo:t.replyTo,subject:t.subject,text:t.text,html:t.html,attachments:t.attachments});return{accepted:n.accepted,rejected:n.rejected,messageId:n.messageId}}catch(e){throw e.code==="MODULE_NOT_FOUND"?new Error("SMTP driver requires nodemailer. Install: npm install nodemailer"):e}}},y=class{constructor(t){this.config=t}async send(t){let e=this.config.apiToken;if(!e)throw new Error("Postmark apiToken is required. Set it in your mailer config or POSTMARK_API_TOKEN env var.");let r=d(t.to),n=d(t.cc),i=d(t.bcc),s={From:t.from?M(t.from):void 0,To:r.join(", "),Subject:t.subject,MessageStream:this.config.messageStream||"outbound"};n.length>0&&(s.Cc=n.join(", ")),i.length>0&&(s.Bcc=i.join(", ")),t.replyTo&&(s.ReplyTo=t.replyTo),t.html&&(s.HtmlBody=t.html),t.text&&(s.TextBody=t.text),!s.HtmlBody&&!s.TextBody&&(s.TextBody=""),t.tags&&(s.Tag=Object.values(t.tags)[0]),t.attachments?.length&&(s.Attachments=t.attachments.map(o=>({Name:o.filename,Content:P(o.content),ContentType:o.contentType||"application/octet-stream"})));let c=await fetch("https://api.postmarkapp.com/email",{method:"POST",headers:{Accept:"application/json","Content-Type":"application/json","X-Postmark-Server-Token":e},body:JSON.stringify(s)});if(!c.ok){let o=await c.json().catch(()=>({Message:c.statusText}));throw new Error(`Postmark error ${c.status}: ${o.Message||JSON.stringify(o)}`)}let l=await c.json();return{accepted:r,rejected:[],messageId:l.MessageID}}},b=class{constructor(t){this.config=t}async send(t){let e=this.config.apiKey;if(!e)throw new Error("Resend apiKey is required. Set it in your mailer config or RESEND_API_KEY env var.");let r=d(t.to),n=d(t.cc),i=d(t.bcc),s={from:t.from?M(t.from):void 0,to:r,subject:t.subject};n.length>0&&(s.cc=n),i.length>0&&(s.bcc=i),t.replyTo&&(s.reply_to=[t.replyTo]),t.html&&(s.html=t.html),t.text&&(s.text=t.text),t.tags&&(s.tags=Object.entries(t.tags).map(([o,f])=>({name:o,value:f}))),t.attachments?.length&&(s.attachments=t.attachments.map(o=>({filename:o.filename,content:P(o.content),content_type:o.contentType||"application/octet-stream"})));let c=await fetch("https://api.resend.com/emails",{method:"POST",headers:{Authorization:`Bearer ${e}`,"Content-Type":"application/json"},body:JSON.stringify(s)});if(!c.ok){let o=await c.json().catch(()=>({message:c.statusText}));throw new Error(`Resend error ${c.status}: ${o.message||JSON.stringify(o)}`)}let l=await c.json();return{accepted:r,rejected:[],messageId:l.id}}},C=class{message={};to(t){return this.message.to=t,this}cc(t){return this.message.cc=t,this}bcc(t){return this.message.bcc=t,this}from(t){return this.message.from=t,this}replyTo(t){return this.message.replyTo=t,this}subject(t){return this.message.subject=t,this}text(t){return this.message.text=t,this}html(t){return this.message.html=t,this}attach(t,e,r){return this.message.attachments||(this.message.attachments=[]),this.message.attachments.push({filename:t,content:e,contentType:r}),this}tag(t,e){return this.message.tags||(this.message.tags={}),this.message.tags[t]=e,this}toMessage(){return this.message}},v=class{config=null;transports=new Map;configure(t){this.config=t,this.transports.clear()}async send(t,e){let r=this.resolveTransport(e);return!t.from&&this.config?.from&&(t.from=this.config.from),r.send(t)}async sendMailable(t,e){t.build();let r=t.toMessage();return!r.from&&this.config?.from&&(r.from=this.config.from),this.send(r,e)}mailer(t){let e=this.resolveTransport(t);return{send:r=>(!r.from&&this.config?.from&&(r.from=this.config.from),e.send(r))}}resolveTransport(t){let e=t??this.config?.default??"log";if(this.transports.has(e))return this.transports.get(e);if(!this.config){let i=new p;return this.transports.set(e,i),i}let r=this.config.mailers[e];if(!r)throw new Error(`Mailer "${e}" is not defined.`);let n;switch(r.driver){case"smtp":n=new w(r);break;case"postmark":n=new y(r);break;case"resend":n=new b(r);break;case"log":n=new p;break;case"null":n=new m;break;case"custom":{if(!r.transport)throw new Error(`Custom mail driver "${e}" requires a "transport" instance.`);n=r.transport;break}default:throw new Error(`Unknown mail driver: ${r.driver}`)}return this.transports.set(e,n),n}},k=h("svelar.mail",()=>new v)});var z={};x(z,{Connection:()=>I});var D,I,E=u(()=>{"use strict";g();D=class{connections=new Map;config=null;defaultName="default";configure(t){this.config=t,this.defaultName=t.default}async connection(t){let e=t??this.defaultName;if(this.connections.has(e))return this.connections.get(e).drizzle;if(!this.config)throw new Error("Database not configured. Call Connection.configure() first, or register DatabaseServiceProvider.");let r=this.config.connections[e];if(!r)throw new Error(`Database connection "${e}" is not defined in configuration.`);let n=await this.createConnection(r);return this.connections.set(e,n),n.drizzle}async rawClient(t){let e=t??this.defaultName;return await this.connection(e),this.connections.get(e).rawClient}async raw(t,e=[],r){let n=await this.connection(r),i=this.getConfig(r);switch(i.driver){case"sqlite":{let s=await this.rawClient(r),c=e.map(f=>typeof f=="boolean"?f?1:0:f instanceof Date?f.toISOString():f),l=s.prepare(t),o=t.trimStart().toUpperCase();return o.startsWith("SELECT")||o.startsWith("PRAGMA")||o.startsWith("WITH")?l.all(...c):l.run(...c)}case"postgres":return(await this.rawClient(r))(t,...e);case"mysql":{let s=await this.rawClient(r),[c]=await s.execute(t,e);return c}default:throw new Error(`Unsupported driver: ${i.driver}`)}}getDriver(t){return this.getConfig(t).driver}getConfig(t){let e=t??this.defaultName;if(!this.config)throw new Error("Database not configured.");let r=this.config.connections[e];if(!r)throw new Error(`Database connection "${e}" is not defined.`);return r}async disconnect(t){if(t){let e=this.connections.get(t);e&&(await this.closeConnection(e),this.connections.delete(t))}else{for(let[e,r]of this.connections)await this.closeConnection(r);this.connections.clear()}}isConnected(t){return this.connections.has(t??this.defaultName)}async transaction(t,e){let r=this.getConfig(e),n=await this.rawClient(e);switch(r.driver){case"sqlite":{n.exec("BEGIN");try{let i=await t();return n.exec("COMMIT"),i}catch(i){throw n.exec("ROLLBACK"),i}}case"postgres":{await n`BEGIN`;try{let i=await t();return await n`COMMIT`,i}catch(i){throw await n`ROLLBACK`,i}}case"mysql":{let i=await n.getConnection();await i.beginTransaction();try{let s=await t();return await i.commit(),i.release(),s}catch(s){throw await i.rollback(),i.release(),s}}default:throw new Error(`Unsupported driver: ${r.driver}`)}}async createConnection(t){switch(t.driver){case"sqlite":return this.createSQLiteConnection(t);case"postgres":return this.createPostgresConnection(t);case"mysql":return this.createMySQLConnection(t);default:throw new Error(`Unsupported database driver: ${t.driver}`)}}async createSQLiteConnection(t){let e=t.filename??t.database??":memory:";try{let r=(await import("better-sqlite3")).default,{drizzle:n}=await import("drizzle-orm/better-sqlite3"),i=new r(e);return i.pragma("journal_mode = WAL"),i.pragma("foreign_keys = ON"),{drizzle:n(i),config:t,rawClient:i}}catch(r){let n;try{n=(await new Function("mod","return import(mod)")("node:sqlite")).DatabaseSync}catch{throw new Error(`No SQLite driver available. Install better-sqlite3 (npm install better-sqlite3) or use Node.js v22+ which includes built-in SQLite support. Original error: ${r instanceof Error?r.message:String(r)}`)}let i=new n(e);i.exec("PRAGMA journal_mode = WAL"),i.exec("PRAGMA foreign_keys = ON");let s={prepare(l){let o=i.prepare(l);return{all(...f){return o.all(...f)},run(...f){return o.run(...f)},get(...f){return o.get(...f)}}},exec(l){i.exec(l)},pragma(l){return i.prepare(`PRAGMA ${l}`).all()},close(){i.close()}},c;try{let{drizzle:l}=await import("drizzle-orm/better-sqlite3");c=l(s)}catch{c=s}return{drizzle:c,config:t,rawClient:s}}}async createPostgresConnection(t){let e=(await import("postgres")).default,{drizzle:r}=await import("drizzle-orm/postgres-js"),n=t.url??`postgres://${t.user}:${t.password}@${t.host??"localhost"}:${t.port??5432}/${t.database}`,i=e(n);return{drizzle:r(i),config:t,rawClient:i}}async createMySQLConnection(t){let e=await import("mysql2/promise"),{drizzle:r}=await import("drizzle-orm/mysql2"),n=e.createPool({host:t.host??"localhost",port:t.port??3306,database:t.database,user:t.user,password:t.password,uri:t.url});return{drizzle:r(n),config:t,rawClient:n}}async closeConnection(t){try{switch(t.config.driver){case"sqlite":t.rawClient.close();break;case"postgres":await t.rawClient.end();break;case"mysql":await t.rawClient.end();break}}catch{}}},I=h("svelar.connection",()=>new D)});g();var $=class{},N=class{async send(t,e){if(!e.toMail)return;let r=e.toMail(t),n=t.routeNotificationForMail?.()??t.getAttribute("email");if(!n){console.warn("[Notifications] No email address for notifiable.");return}try{let{Mailer:i}=await Promise.resolve().then(()=>(A(),j));await i.send({to:n,subject:r.subject,html:r.html,text:r.text,from:r.from})}catch(i){console.error("[Notifications] Failed to send mail notification:",i)}}},T=class{table;constructor(t="notifications"){this.table=t}async send(t,e){if(!e.toDatabase)return;let r=e.toDatabase(t);try{let{Connection:n}=await Promise.resolve().then(()=>(E(),z));await n.raw(`INSERT INTO ${this.table} (id, notifiable_id, type, data, read_at, created_at)
|
|
2
|
+
VALUES (?, ?, ?, ?, NULL, ?)`,[crypto.randomUUID(),t.getAttribute("id"),r.type,JSON.stringify(r.data),new Date().toISOString()])}catch(n){console.error("[Notifications] Failed to store database notification:",n)}}},S=class{channels=new Map;constructor(){this.channels.set("mail",new N),this.channels.set("database",new T)}extend(t,e){this.channels.set(t,e)}async send(t,e){let r=Array.isArray(t)?t:[t];for(let n of r){let i=e.via(n);for(let s of i){let c=this.channels.get(s);if(c)try{await c.send(n,e)}catch(l){console.error(`[Notifications] Channel "${s}" failed:`,l)}else console.warn(`[Notifications] Unknown channel: ${s}`)}}}async sendVia(t,e,r){for(let n of r){let i=this.channels.get(n);i&&await i.send(t,e)}}},U=h("svelar.notifier",()=>new S);export{$ as Notification,U as Notifier};
|