@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,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ESM resolve hook for Svelar CLI:
|
|
3
|
+
* 1. Resolves 'svelar' and 'svelar/*' imports directly from THIS package's dist/
|
|
4
|
+
* (bypasses node_modules entirely — no symlinks, junctions, or platform issues)
|
|
5
|
+
* 2. Rewrites .js → .ts when the .js file doesn't exist
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { existsSync } from 'node:fs';
|
|
9
|
+
import { resolve as pathResolve, sep, dirname } from 'node:path';
|
|
10
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
11
|
+
|
|
12
|
+
// This file lives at <svelar-root>/dist/cli/ts-resolve-hook.mjs
|
|
13
|
+
// So the svelar package root is two directories up from here.
|
|
14
|
+
const thisFileDir = dirname(fileURLToPath(import.meta.url));
|
|
15
|
+
const svelarRoot = pathResolve(thisFileDir, '..', '..');
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Resolve 'svelar' or 'svelar/<subpath>' by pointing directly at our own dist/.
|
|
19
|
+
* This works on every OS because it never touches node_modules or symlinks.
|
|
20
|
+
*/
|
|
21
|
+
function resolveSvelar(specifier) {
|
|
22
|
+
if (specifier === 'svelar') {
|
|
23
|
+
const target = pathResolve(svelarRoot, 'dist', 'index.js');
|
|
24
|
+
if (existsSync(target)) return pathToFileURL(target).href;
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (specifier.startsWith('svelar/')) {
|
|
29
|
+
const subpath = specifier.slice('svelar/'.length); // e.g. 'database', 'orm', 'auth'
|
|
30
|
+
const target = pathResolve(svelarRoot, 'dist', subpath, 'index.js');
|
|
31
|
+
if (existsSync(target)) return pathToFileURL(target).href;
|
|
32
|
+
|
|
33
|
+
// Also try direct file (e.g. svelar/something.js)
|
|
34
|
+
const directTarget = pathResolve(svelarRoot, 'dist', subpath + '.js');
|
|
35
|
+
if (existsSync(directTarget)) return pathToFileURL(directTarget).href;
|
|
36
|
+
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export async function resolve(specifier, context, nextResolve) {
|
|
44
|
+
// 1. Intercept svelar imports FIRST — before Node tries (and fails) to resolve them
|
|
45
|
+
if (specifier === 'svelar' || specifier.startsWith('svelar/')) {
|
|
46
|
+
const resolved = resolveSvelar(specifier);
|
|
47
|
+
if (resolved) {
|
|
48
|
+
return { url: resolved, shortCircuit: true };
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// 2. Handle relative .js → .ts resolution
|
|
53
|
+
if (specifier.startsWith('.') && specifier.endsWith('.js')) {
|
|
54
|
+
try {
|
|
55
|
+
const parentPath = context.parentURL ? fileURLToPath(context.parentURL) : process.cwd();
|
|
56
|
+
const parentDir = parentPath.endsWith(sep) || parentPath.endsWith('/')
|
|
57
|
+
? parentPath
|
|
58
|
+
: dirname(parentPath);
|
|
59
|
+
const jsPath = pathResolve(parentDir, specifier);
|
|
60
|
+
|
|
61
|
+
if (!existsSync(jsPath)) {
|
|
62
|
+
const tsPath = jsPath.replace(/\.js$/, '.ts');
|
|
63
|
+
if (existsSync(tsPath)) {
|
|
64
|
+
return { url: pathToFileURL(tsPath).href, shortCircuit: true };
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
} catch {
|
|
68
|
+
// Fall through
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// 3. Default resolution
|
|
73
|
+
return nextResolve(specifier, context);
|
|
74
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Svelar Config
|
|
3
|
+
*
|
|
4
|
+
* Environment-aware configuration management with optional
|
|
5
|
+
* directory-based config loading (like Laravel's config/ folder).
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Get an environment variable with an optional default.
|
|
9
|
+
* Similar to Laravel's env() helper.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* env('DB_HOST', 'localhost') // string
|
|
13
|
+
* env<number>('DB_PORT', 5432) // number
|
|
14
|
+
* env<boolean>('APP_DEBUG', false) // boolean
|
|
15
|
+
*/
|
|
16
|
+
export declare function env<T extends string | number | boolean = string>(key: string, defaultValue?: T): T;
|
|
17
|
+
declare class ConfigManager {
|
|
18
|
+
private items;
|
|
19
|
+
/**
|
|
20
|
+
* Clear all configuration
|
|
21
|
+
*/
|
|
22
|
+
clear(): void;
|
|
23
|
+
/**
|
|
24
|
+
* Load configuration from an object
|
|
25
|
+
*/
|
|
26
|
+
load(config: Record<string, any>): void;
|
|
27
|
+
/**
|
|
28
|
+
* Load all config files from a directory.
|
|
29
|
+
* Each file becomes a top-level config key based on its filename.
|
|
30
|
+
*
|
|
31
|
+
* Files must export a default object (or a named `config` export).
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* // config/app.ts → config.get('app.name')
|
|
35
|
+
* // config/database.ts → config.get('database.default')
|
|
36
|
+
* // config/mail.ts → config.get('mail.driver')
|
|
37
|
+
*
|
|
38
|
+
* await config.loadFromDirectory('./config');
|
|
39
|
+
*
|
|
40
|
+
* @param dirPath - Path to the config directory (relative to cwd or absolute)
|
|
41
|
+
*/
|
|
42
|
+
loadFromDirectory(dirPath: string): Promise<string[]>;
|
|
43
|
+
/**
|
|
44
|
+
* Get a configuration value using dot notation
|
|
45
|
+
* @example config.get('database.default') // 'sqlite'
|
|
46
|
+
*/
|
|
47
|
+
get<T = any>(key: string, defaultValue?: T): T;
|
|
48
|
+
/**
|
|
49
|
+
* Set a configuration value using dot notation
|
|
50
|
+
*/
|
|
51
|
+
set(key: string, value: any): void;
|
|
52
|
+
/**
|
|
53
|
+
* Check if a configuration key exists
|
|
54
|
+
*/
|
|
55
|
+
has(key: string): boolean;
|
|
56
|
+
/**
|
|
57
|
+
* Get all configuration as a plain object
|
|
58
|
+
*/
|
|
59
|
+
all(): Record<string, any>;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Global config singleton
|
|
63
|
+
*/
|
|
64
|
+
export declare const config: ConfigManager;
|
|
65
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { config, env } from './Config.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
function h(a,n){let t=Symbol.for(a),e=globalThis;return e[t]||(e[t]=n()),e[t]}function T(a,n){let t=process.env[a];return t===void 0?n!==void 0?n:"":t==="true"?!0:t==="false"?!1:t==="null"?null:/^\d+$/.test(t)?Number(t):t}var u=class{items=new Map;clear(){this.items.clear()}load(n){for(let[t,e]of Object.entries(n))this.set(t,e)}async loadFromDirectory(n){let{resolve:t,basename:e,extname:s}=await import("path"),{existsSync:r,readdirSync:i}=await import("fs"),{pathToFileURL:m}=await import("url"),l=t(n);if(!r(l))return[];let y=i(l).filter(o=>(o.endsWith(".ts")||o.endsWith(".js"))&&!o.startsWith(".")),g=[];for(let o of y){let d=e(o,s(o)),p=t(l,o);try{let f=await import(m(p).href),c=f.default??f.config??f;c&&typeof c=="object"&&!Array.isArray(c)&&(this.set(d,c),g.push(d))}catch{}}return g}get(n,t){let e=n.split("."),s=this.items.get(e[0]);for(let r=1;r<e.length;r++){if(s==null)return t;s=s[e[r]]}return s??t}set(n,t){let e=n.split(".");if(e.length===1){this.items.set(n,t);return}let s=this.items.get(e[0]);(s===void 0||typeof s!="object")&&(s={},this.items.set(e[0],s));let r=s;for(let i=1;i<e.length-1;i++)(r[e[i]]===void 0||typeof r[e[i]]!="object")&&(r[e[i]]={}),r=r[e[i]];r[e[e.length-1]]=t}has(n){return this.get(n)!==void 0}all(){let n={};for(let[t,e]of this.items)n[t]=e;return n}},b=h("svelar.config",()=>new u);export{b as config,T as env};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Svelar Application
|
|
3
|
+
*
|
|
4
|
+
* Bootstraps the framework by registering and booting all service providers.
|
|
5
|
+
*/
|
|
6
|
+
import { Container } from './Container.js';
|
|
7
|
+
import { ServiceProvider } from './ServiceProvider.js';
|
|
8
|
+
export declare class Application {
|
|
9
|
+
readonly container: Container;
|
|
10
|
+
private providers;
|
|
11
|
+
private booted;
|
|
12
|
+
constructor(container?: Container);
|
|
13
|
+
/**
|
|
14
|
+
* Register a service provider
|
|
15
|
+
*/
|
|
16
|
+
register(ProviderClass: new (app: Container) => ServiceProvider): this;
|
|
17
|
+
/**
|
|
18
|
+
* Bootstrap the application: register all, then boot all
|
|
19
|
+
*/
|
|
20
|
+
bootstrap(): Promise<this>;
|
|
21
|
+
/**
|
|
22
|
+
* Check if the application has been bootstrapped
|
|
23
|
+
*/
|
|
24
|
+
isBooted(): boolean;
|
|
25
|
+
/**
|
|
26
|
+
* Resolve a service from the container
|
|
27
|
+
*/
|
|
28
|
+
make<T = any>(name: string): Promise<T>;
|
|
29
|
+
/**
|
|
30
|
+
* Get the list of registered providers
|
|
31
|
+
*/
|
|
32
|
+
getProviders(): ServiceProvider[];
|
|
33
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Svelar IoC Container
|
|
3
|
+
*
|
|
4
|
+
* Laravel-inspired service container with dependency injection,
|
|
5
|
+
* singleton support, and service provider lifecycle.
|
|
6
|
+
*/
|
|
7
|
+
export type Factory<T = any> = (container: Container) => T;
|
|
8
|
+
export type AsyncFactory<T = any> = (container: Container) => T | Promise<T>;
|
|
9
|
+
export declare class Container {
|
|
10
|
+
private bindings;
|
|
11
|
+
private aliases;
|
|
12
|
+
private resolved;
|
|
13
|
+
/**
|
|
14
|
+
* Register a binding in the container
|
|
15
|
+
*/
|
|
16
|
+
bind<T>(name: string, factory: AsyncFactory<T>): void;
|
|
17
|
+
/**
|
|
18
|
+
* Register a singleton binding (resolved once, then cached)
|
|
19
|
+
*/
|
|
20
|
+
singleton<T>(name: string, factory: AsyncFactory<T>): void;
|
|
21
|
+
/**
|
|
22
|
+
* Register an existing instance as a singleton
|
|
23
|
+
*/
|
|
24
|
+
instance<T>(name: string, value: T): void;
|
|
25
|
+
/**
|
|
26
|
+
* Create an alias for a binding
|
|
27
|
+
*/
|
|
28
|
+
alias(alias: string, target: string): void;
|
|
29
|
+
/**
|
|
30
|
+
* Resolve a binding from the container
|
|
31
|
+
*/
|
|
32
|
+
make<T = any>(name: string): Promise<T>;
|
|
33
|
+
/**
|
|
34
|
+
* Synchronous resolve (only works for already-resolved singletons or sync factories)
|
|
35
|
+
*/
|
|
36
|
+
makeSync<T = any>(name: string): T;
|
|
37
|
+
/**
|
|
38
|
+
* Check if a binding exists
|
|
39
|
+
*/
|
|
40
|
+
has(name: string): boolean;
|
|
41
|
+
/**
|
|
42
|
+
* Check if a binding has been resolved
|
|
43
|
+
*/
|
|
44
|
+
isResolved(name: string): boolean;
|
|
45
|
+
/**
|
|
46
|
+
* Tag bindings for group resolution
|
|
47
|
+
*/
|
|
48
|
+
tag(names: string[], tag: string): void;
|
|
49
|
+
/**
|
|
50
|
+
* Resolve all bindings with a given tag
|
|
51
|
+
*/
|
|
52
|
+
tagged<T = any>(tag: string): Promise<T[]>;
|
|
53
|
+
/**
|
|
54
|
+
* Flush all resolved singletons (useful for testing)
|
|
55
|
+
*/
|
|
56
|
+
flush(): void;
|
|
57
|
+
/**
|
|
58
|
+
* Remove a binding entirely
|
|
59
|
+
*/
|
|
60
|
+
forget(name: string): void;
|
|
61
|
+
/**
|
|
62
|
+
* Get all binding names
|
|
63
|
+
*/
|
|
64
|
+
getBindings(): string[];
|
|
65
|
+
private resolveAlias;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Global application container
|
|
69
|
+
*/
|
|
70
|
+
export declare const container: Container;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Svelar Service Provider
|
|
3
|
+
*
|
|
4
|
+
* Base class for registering services into the IoC container.
|
|
5
|
+
* Follows Laravel's register/boot lifecycle.
|
|
6
|
+
*/
|
|
7
|
+
import { Container } from './Container.js';
|
|
8
|
+
export declare abstract class ServiceProvider {
|
|
9
|
+
protected app: Container;
|
|
10
|
+
constructor(app: Container);
|
|
11
|
+
/**
|
|
12
|
+
* Register bindings into the container.
|
|
13
|
+
* Called before any provider is booted.
|
|
14
|
+
*/
|
|
15
|
+
abstract register(): void | Promise<void>;
|
|
16
|
+
/**
|
|
17
|
+
* Bootstrap services after all providers are registered.
|
|
18
|
+
* Use this for logic that depends on other services being available.
|
|
19
|
+
*/
|
|
20
|
+
boot(): void | Promise<void>;
|
|
21
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
function c(r,n){let i=Symbol.for(r),t=globalThis;return t[i]||(t[i]=n()),t[i]}var e=class{bindings=new Map;aliases=new Map;resolved=new Set;bind(n,i){this.bindings.set(n,{factory:i,singleton:!1,tags:[]})}singleton(n,i){this.bindings.set(n,{factory:i,singleton:!0,tags:[]})}instance(n,i){this.bindings.set(n,{factory:()=>i,singleton:!0,instance:i,tags:[]})}alias(n,i){this.aliases.set(n,i)}async make(n){let i=this.resolveAlias(n),t=this.bindings.get(i);if(!t)throw new Error(`No binding found for "${n}" in the container.`);if(t.singleton&&t.instance!==void 0)return t.instance;let s=await t.factory(this);return t.singleton&&(t.instance=s),this.resolved.add(i),s}makeSync(n){let i=this.resolveAlias(n),t=this.bindings.get(i);if(!t)throw new Error(`No binding found for "${n}" in the container.`);if(t.singleton&&t.instance!==void 0)return t.instance;let s=t.factory(this);if(s instanceof Promise)throw new Error(`Binding "${n}" has an async factory. Use container.make() instead of container.makeSync().`);return t.singleton&&(t.instance=s),this.resolved.add(i),s}has(n){let i=this.resolveAlias(n);return this.bindings.has(i)}isResolved(n){return this.resolved.has(this.resolveAlias(n))}tag(n,i){for(let t of n){let s=this.bindings.get(t);s&&s.tags.push(i)}}async tagged(n){let i=[];for(let[t,s]of this.bindings)s.tags.includes(n)&&i.push(await this.make(t));return i}flush(){for(let n of this.bindings.values())n.singleton&&(n.instance=void 0);this.resolved.clear()}forget(n){this.bindings.delete(n),this.resolved.delete(n)}getBindings(){return[...this.bindings.keys()]}resolveAlias(n){return this.aliases.get(n)??n}},d=c("svelar.container",()=>new e);var o=class{app;constructor(n){this.app=n}boot(){}};var a=class{container;providers=[];booted=!1;constructor(n){this.container=n??new e,this.container.instance("app",this),this.container.instance("container",this.container)}register(n){let i=new n(this.container);return this.providers.push(i),this}async bootstrap(){if(this.booted)return this;for(let n of this.providers)await n.register();for(let n of this.providers)await n.boot();return this.booted=!0,this}isBooted(){return this.booted}async make(n){return this.container.make(n)}getProviders(){return[...this.providers]}};export{a as Application,e as Container,o as ServiceProvider,d as container};
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Svelar Admin Dashboard Plugin
|
|
3
|
+
*
|
|
4
|
+
* Provides a centralized admin dashboard that aggregates system health,
|
|
5
|
+
* queue metrics, scheduler status, and logs for monitoring and management.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```ts
|
|
9
|
+
* import { Dashboard, configureDashboard, getDashboardHealth } from 'svelar/dashboard';
|
|
10
|
+
*
|
|
11
|
+
* // Configure the dashboard
|
|
12
|
+
* configureDashboard({
|
|
13
|
+
* enabled: true,
|
|
14
|
+
* prefix: '/admin/dashboard',
|
|
15
|
+
* refreshInterval: 5000,
|
|
16
|
+
* });
|
|
17
|
+
*
|
|
18
|
+
* // Get system health
|
|
19
|
+
* const health = getDashboardHealth();
|
|
20
|
+
* console.log(`Uptime: ${health.uptime}ms`);
|
|
21
|
+
*
|
|
22
|
+
* // Get full dashboard data
|
|
23
|
+
* const data = Dashboard.getDashboardData();
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
import type { Container } from '../container/Container.js';
|
|
27
|
+
import { Plugin } from '../plugins/index.js';
|
|
28
|
+
import { type QueueHealth } from '../queue/JobMonitor.js';
|
|
29
|
+
import { type SchedulerHealth } from '../scheduler/ScheduleMonitor.js';
|
|
30
|
+
export interface DashboardConfig {
|
|
31
|
+
/** Enable or disable the dashboard */
|
|
32
|
+
enabled?: boolean;
|
|
33
|
+
/** Route prefix for dashboard endpoints (default: '/admin/dashboard') */
|
|
34
|
+
prefix?: string;
|
|
35
|
+
/** Interval in ms for automatic health collection (default: 5000) */
|
|
36
|
+
refreshInterval?: number;
|
|
37
|
+
/** Section visibility toggles */
|
|
38
|
+
sections?: {
|
|
39
|
+
logs?: boolean;
|
|
40
|
+
queue?: boolean;
|
|
41
|
+
scheduler?: boolean;
|
|
42
|
+
system?: boolean;
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
export interface SystemHealth {
|
|
46
|
+
status: 'healthy' | 'degraded' | 'critical';
|
|
47
|
+
uptime: number;
|
|
48
|
+
nodeVersion: string;
|
|
49
|
+
memoryUsage: {
|
|
50
|
+
heapUsed: number;
|
|
51
|
+
heapTotal: number;
|
|
52
|
+
rss: number;
|
|
53
|
+
external: number;
|
|
54
|
+
};
|
|
55
|
+
cpuUsage: {
|
|
56
|
+
user: number;
|
|
57
|
+
system: number;
|
|
58
|
+
};
|
|
59
|
+
queueHealth: QueueHealth;
|
|
60
|
+
schedulerHealth: SchedulerHealth;
|
|
61
|
+
logStats: {
|
|
62
|
+
totalEntries: number;
|
|
63
|
+
byLevel: Record<string, number>;
|
|
64
|
+
byChannel: Record<string, number>;
|
|
65
|
+
};
|
|
66
|
+
timestamp: string;
|
|
67
|
+
}
|
|
68
|
+
declare class DashboardPlugin extends Plugin {
|
|
69
|
+
readonly name = "svelar-dashboard";
|
|
70
|
+
readonly version = "1.0.0";
|
|
71
|
+
readonly description = "Admin dashboard with system health monitoring";
|
|
72
|
+
private dashboardConfig;
|
|
73
|
+
private startTime;
|
|
74
|
+
private healthCollectionInterval;
|
|
75
|
+
/**
|
|
76
|
+
* Return plugin configuration
|
|
77
|
+
*/
|
|
78
|
+
config(): {
|
|
79
|
+
key: string;
|
|
80
|
+
defaults: Required<DashboardConfig>;
|
|
81
|
+
};
|
|
82
|
+
/**
|
|
83
|
+
* Register services and configuration
|
|
84
|
+
*/
|
|
85
|
+
register(app: Container): Promise<void>;
|
|
86
|
+
/**
|
|
87
|
+
* Bootstrap the dashboard plugin
|
|
88
|
+
*/
|
|
89
|
+
boot(app: Container): Promise<void>;
|
|
90
|
+
/**
|
|
91
|
+
* Clean up on shutdown
|
|
92
|
+
*/
|
|
93
|
+
shutdown(): Promise<void>;
|
|
94
|
+
/**
|
|
95
|
+
* Update dashboard configuration
|
|
96
|
+
*/
|
|
97
|
+
setConfig(config: DashboardConfig): void;
|
|
98
|
+
/**
|
|
99
|
+
* Get aggregated system health from all monitors
|
|
100
|
+
*/
|
|
101
|
+
getSystemHealth(): Promise<SystemHealth>;
|
|
102
|
+
/**
|
|
103
|
+
* Get complete dashboard data with all sections
|
|
104
|
+
*/
|
|
105
|
+
getDashboardData(): Promise<Record<string, any>>;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Global Dashboard singleton
|
|
109
|
+
*/
|
|
110
|
+
export declare const Dashboard: DashboardPlugin;
|
|
111
|
+
/**
|
|
112
|
+
* Configure the dashboard
|
|
113
|
+
*/
|
|
114
|
+
export declare function configureDashboard(config: DashboardConfig): void;
|
|
115
|
+
/**
|
|
116
|
+
* Quick access to system health
|
|
117
|
+
*/
|
|
118
|
+
export declare function getDashboardHealth(): Promise<SystemHealth>;
|
|
119
|
+
/**
|
|
120
|
+
* Quick access to full dashboard data
|
|
121
|
+
*/
|
|
122
|
+
export declare function getDashboardData(): Promise<Record<string, any>>;
|
|
123
|
+
export {};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
var x=Object.defineProperty;var T=(l,e)=>()=>(l&&(e=l(l=0)),e);var N=(l,e)=>{for(var t in e)x(l,t,{get:e[t],enumerable:!0})};function d(l,e){let t=Symbol.for(l),s=globalThis;return s[t]||(s[t]=e()),s[t]}var g=T(()=>{"use strict"});var f={};N(f,{Connection:()=>A});var C,A,p=T(()=>{"use strict";g();C=class{connections=new Map;config=null;defaultName="default";configure(e){this.config=e,this.defaultName=e.default}async connection(e){let t=e??this.defaultName;if(this.connections.has(t))return this.connections.get(t).drizzle;if(!this.config)throw new Error("Database not configured. Call Connection.configure() first, or register DatabaseServiceProvider.");let s=this.config.connections[t];if(!s)throw new Error(`Database connection "${t}" is not defined in configuration.`);let n=await this.createConnection(s);return this.connections.set(t,n),n.drizzle}async rawClient(e){let t=e??this.defaultName;return await this.connection(t),this.connections.get(t).rawClient}async raw(e,t=[],s){let n=await this.connection(s),r=this.getConfig(s);switch(r.driver){case"sqlite":{let i=await this.rawClient(s),a=t.map(c=>typeof c=="boolean"?c?1:0:c instanceof Date?c.toISOString():c),o=i.prepare(e),u=e.trimStart().toUpperCase();return u.startsWith("SELECT")||u.startsWith("PRAGMA")||u.startsWith("WITH")?o.all(...a):o.run(...a)}case"postgres":return(await this.rawClient(s))(e,...t);case"mysql":{let i=await this.rawClient(s),[a]=await i.execute(e,t);return a}default:throw new Error(`Unsupported driver: ${r.driver}`)}}getDriver(e){return this.getConfig(e).driver}getConfig(e){let t=e??this.defaultName;if(!this.config)throw new Error("Database not configured.");let s=this.config.connections[t];if(!s)throw new Error(`Database connection "${t}" is not defined.`);return s}async disconnect(e){if(e){let t=this.connections.get(e);t&&(await this.closeConnection(t),this.connections.delete(e))}else{for(let[t,s]of this.connections)await this.closeConnection(s);this.connections.clear()}}isConnected(e){return this.connections.has(e??this.defaultName)}async transaction(e,t){let s=this.getConfig(t),n=await this.rawClient(t);switch(s.driver){case"sqlite":{n.exec("BEGIN");try{let r=await e();return n.exec("COMMIT"),r}catch(r){throw n.exec("ROLLBACK"),r}}case"postgres":{await n`BEGIN`;try{let r=await e();return await n`COMMIT`,r}catch(r){throw await n`ROLLBACK`,r}}case"mysql":{let r=await n.getConnection();await r.beginTransaction();try{let i=await e();return await r.commit(),r.release(),i}catch(i){throw await r.rollback(),r.release(),i}}default:throw new Error(`Unsupported driver: ${s.driver}`)}}async createConnection(e){switch(e.driver){case"sqlite":return this.createSQLiteConnection(e);case"postgres":return this.createPostgresConnection(e);case"mysql":return this.createMySQLConnection(e);default:throw new Error(`Unsupported database driver: ${e.driver}`)}}async createSQLiteConnection(e){let t=e.filename??e.database??":memory:";try{let s=(await import("better-sqlite3")).default,{drizzle:n}=await import("drizzle-orm/better-sqlite3"),r=new s(t);return r.pragma("journal_mode = WAL"),r.pragma("foreign_keys = ON"),{drizzle:n(r),config:e,rawClient:r}}catch(s){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: ${s instanceof Error?s.message:String(s)}`)}let r=new n(t);r.exec("PRAGMA journal_mode = WAL"),r.exec("PRAGMA foreign_keys = ON");let i={prepare(o){let u=r.prepare(o);return{all(...c){return u.all(...c)},run(...c){return u.run(...c)},get(...c){return u.get(...c)}}},exec(o){r.exec(o)},pragma(o){return r.prepare(`PRAGMA ${o}`).all()},close(){r.close()}},a;try{let{drizzle:o}=await import("drizzle-orm/better-sqlite3");a=o(i)}catch{a=i}return{drizzle:a,config:e,rawClient:i}}}async createPostgresConnection(e){let t=(await import("postgres")).default,{drizzle:s}=await import("drizzle-orm/postgres-js"),n=e.url??`postgres://${e.user}:${e.password}@${e.host??"localhost"}:${e.port??5432}/${e.database}`,r=t(n);return{drizzle:s(r),config:e,rawClient:r}}async createMySQLConnection(e){let t=await import("mysql2/promise"),{drizzle:s}=await import("drizzle-orm/mysql2"),n=t.createPool({host:e.host??"localhost",port:e.port??3306,database:e.database,user:e.user,password:e.password,uri:e.url});return{drizzle:s(n),config:e,rawClient:n}}async closeConnection(e){try{switch(e.config.driver){case"sqlite":e.rawClient.close();break;case"postgres":await e.rawClient.end();break;case"mysql":await e.rawClient.end();break}}catch{}}},A=d("svelar.connection",()=>new C)});import{hostname as O}from"os";var Q,L=T(()=>{"use strict";Q=`${O()}:${process.pid}:${Math.random().toString(36).slice(2,10)}`});var y=class{description;dependencies;async register(e){}async boot(e){}async shutdown(){}migrations(){return[]}config(){return null}middleware(){return[]}commands(){return[]}routes(){return[]}listeners(){return[]}publishables(){return{}}};g();g();var E=class{ringBuffer=[];maxSize=1e4;currentIndex=0;tailSubscribers=[];addEntry(e){let t={timestamp:e.timestamp,level:e.level,channel:e.channel??"default",message:e.message,context:e.context};this.ringBuffer.length<this.maxSize?this.ringBuffer.push(t):(this.ringBuffer[this.currentIndex]=t,this.currentIndex=(this.currentIndex+1)%this.maxSize);for(let s of this.tailSubscribers)try{s(t)}catch{}}query(e={}){let t=this.getAllEntries();if(e.level){let r={debug:0,info:1,warn:2,error:3,fatal:4},i=r[e.level];t=t.filter(a=>r[a.level]>=i)}if(e.channel&&(t=t.filter(r=>r.channel===e.channel)),e.since){let r=e.since.getTime();t=t.filter(i=>new Date(i.timestamp).getTime()>=r)}if(e.until){let r=e.until.getTime();t=t.filter(i=>new Date(i.timestamp).getTime()<=r)}if(e.search){let r=e.search.toLowerCase();t=t.filter(i=>i.message.toLowerCase().includes(r)||JSON.stringify(i.context).toLowerCase().includes(r))}let s=e.offset??0,n=e.limit??100;return t.slice(s,s+n)}getStats(){let e=this.getAllEntries(),t={totalEntries:e.length,byLevel:{},byChannel:{}};for(let s of e)t.byLevel[s.level]=(t.byLevel[s.level]??0)+1,t.byChannel[s.channel]=(t.byChannel[s.channel]??0)+1;return t}getRecentErrors(e=10){return this.getAllEntries().filter(s=>s.level==="error"||s.level==="fatal").slice(-e).reverse()}clear(){this.ringBuffer=[],this.currentIndex=0}tail(e,t){let s=t?n=>{this.matchesFilter(n,t)&&e(n)}:e;return this.tailSubscribers.push(s),()=>{let n=this.tailSubscribers.indexOf(s);n>-1&&this.tailSubscribers.splice(n,1)}}getAllEntries(){return this.ringBuffer.length<this.maxSize?[...this.ringBuffer]:[...this.ringBuffer.slice(this.currentIndex),...this.ringBuffer.slice(0,this.currentIndex)]}matchesFilter(e,t){if(t.level){let s={debug:0,info:1,warn:2,error:3,fatal:4},n=s[t.level];if(s[e.level]<n)return!1}if(t.channel&&e.channel!==t.channel)return!1;if(t.since){let s=t.since.getTime();if(new Date(e.timestamp).getTime()<s)return!1}if(t.until){let s=t.until.getTime();if(new Date(e.timestamp).getTime()>s)return!1}if(t.search){let s=t.search.toLowerCase();if(!(e.message.toLowerCase().includes(s)||JSON.stringify(e.context).toLowerCase().includes(s)))return!1}return!0}},m=d("svelar.logViewer",()=>new E);g();var k=class{_config=null;_bullmq=null;_metrics={processed:[],failed:[]};configure(e){this._config=e}recordProcessed(){this._metrics.processed.push(Date.now());let e=Date.now()-36e5;this._metrics.processed=this._metrics.processed.filter(t=>t>e)}recordFailed(){this._metrics.failed.push(Date.now());let e=Date.now()-36e5;this._metrics.failed=this._metrics.failed.filter(t=>t>e)}async getCounts(e="default"){let t=this._config?.connections[this._config.default]?.driver;return t==="redis"?this._getRedisJobCounts(e):t==="database"?this._getDatabaseJobCounts(e):{waiting:0,active:0,completed:0,failed:0,delayed:0,total:0}}async listJobs(e={}){let t=this._config?.connections[this._config.default]?.driver;return t==="redis"?this._listRedisJobs(e):t==="database"?this._listDatabaseJobs(e):[]}async getJob(e){let t=this._config?.connections[this._config.default]?.driver;return t==="redis"?this._getRedisJob(e):t==="database"?this._getDatabaseJob(e):null}async retryJob(e){let t=this._config?.connections[this._config.default]?.driver;return t==="redis"?this._retryRedisJob(e):t==="database"?this._retryDatabaseJob(e):!1}async deleteJob(e){let t=this._config?.connections[this._config.default]?.driver;return t==="redis"?this._deleteRedisJob(e):t==="database"?this._deleteDatabaseJob(e):!1}async getHealth(){let e=this._config?.connections[this._config.default]?.driver??"sync",t=await this.getCounts("default"),s=Date.now()-36e5,n=this._metrics.processed.filter(a=>a>s).length,r=this._metrics.failed.filter(a=>a>s).length,i=n+r;return{driver:e,queues:{default:t},failureRate:i>0?r/i*100:0,throughput:n}}async flushCompleted(e="default"){return this._config?.connections[this._config.default]?.driver==="redis"?this._flushRedisCompleted(e):0}async flushFailed(e="default"){let t=this._config?.connections[this._config.default]?.driver;return t==="redis"?this._flushRedisFailed(e):t==="database"?this._flushDatabaseFailed(e):0}async getBullMQ(){if(this._bullmq)return this._bullmq;try{return this._bullmq=await Function('return import("bullmq")')(),this._bullmq}catch{throw new Error("bullmq is required for the Redis queue monitor.")}}getRedisConnection(){let e=this._config?.connections[this._config.default]??{};if(e.url){let t=new URL(e.url);return{host:t.hostname||"localhost",port:parseInt(t.port)||6379,password:t.password||e.password||void 0,db:parseInt(t.pathname?.slice(1)||"0")||e.db||0}}return{host:e.host??"localhost",port:e.port??6379,password:e.password,db:e.db??0}}async getRedisQueue(e){let t=await this.getBullMQ(),s=this.getRedisConnection(),n=this._config?.connections[this._config.default]??{};return new t.Queue(e,{connection:s,prefix:n.prefix??"svelar"})}async _getRedisJobCounts(e){let t=await this.getRedisQueue(e);try{let s=await t.getJobCounts("waiting","active","completed","failed","delayed");return{waiting:s.waiting??0,active:s.active??0,completed:s.completed??0,failed:s.failed??0,delayed:s.delayed??0,total:(s.waiting??0)+(s.active??0)+(s.completed??0)+(s.failed??0)+(s.delayed??0)}}finally{await t.close()}}async _listRedisJobs(e){let t=await this.getRedisQueue(e.queue??"default"),s=e.limit??50,n=e.offset??0;try{let r;return e.status?r=await t.getJobs([e.status],n,n+s-1):r=await t.getJobs(["waiting","active","completed","failed","delayed"],n,n+s-1),r.filter(i=>i!=null).filter(i=>!e.jobClass||i.name===e.jobClass).map(i=>this._bullmqJobToInfo(i))}finally{await t.close()}}async _getRedisJob(e){let t=await this.getRedisQueue("default");try{let s=await t.getJob(e);return s?this._bullmqJobToInfo(s):null}finally{await t.close()}}async _retryRedisJob(e){let t=await this.getRedisQueue("default");try{let s=await t.getJob(e);return s?(await s.retry(),!0):!1}catch{return!1}finally{await t.close()}}async _deleteRedisJob(e){let t=await this.getRedisQueue("default");try{let s=await t.getJob(e);return s?(await s.remove(),!0):!1}catch{return!1}finally{await t.close()}}async _flushRedisCompleted(e){let t=await this.getRedisQueue(e);try{let s=await t.getJobs(["completed"]),n=0;for(let r of s)r&&(await r.remove(),n++);return n}finally{await t.close()}}async _flushRedisFailed(e){let t=await this.getRedisQueue(e);try{let s=await t.getJobs(["failed"]),n=0;for(let r of s)r&&(await r.remove(),n++);return n}finally{await t.close()}}_bullmqJobToInfo(e){let t=e.finishedOn?e.failedReason?"failed":"completed":e.delay&&e.delay>0&&!e.processedOn?"delayed":e.processedOn?"active":"waiting";return{id:e.id,jobClass:e.name??e.data?.jobClass??"Unknown",queue:e.queueName??"default",status:t,attempts:e.attemptsMade??0,maxAttempts:e.opts?.attempts??3,payload:JSON.stringify(e.data??{}),error:e.failedReason??void 0,createdAt:e.timestamp??Date.now(),processedAt:e.processedOn??void 0,failedAt:e.failedReason?e.finishedOn:void 0,delayedUntil:e.delay?e.timestamp+e.delay:void 0}}async getDbConnection(){let{Connection:e}=await Promise.resolve().then(()=>(p(),f));return e}getDbTable(){return this._config?.connections[this._config.default]?.table??"svelar_jobs"}async _getDatabaseJobCounts(e){let t=await this.getDbConnection(),s=this.getDbTable(),n=Math.floor(Date.now()/1e3),i=(await t.raw(`SELECT
|
|
2
|
+
SUM(CASE WHEN reserved_at IS NULL AND available_at <= ? THEN 1 ELSE 0 END) as waiting,
|
|
3
|
+
SUM(CASE WHEN reserved_at IS NOT NULL THEN 1 ELSE 0 END) as active,
|
|
4
|
+
SUM(CASE WHEN reserved_at IS NULL AND available_at > ? THEN 1 ELSE 0 END) as delayed
|
|
5
|
+
FROM ${s} WHERE queue = ?`,[n,n,e]))?.[0]??{},a=Number(i.waiting)||0,o=Number(i.active)||0,u=Number(i.delayed)||0;return{waiting:a,active:o,completed:0,failed:0,delayed:u,total:a+o+u}}async _listDatabaseJobs(e){let t=await this.getDbConnection(),s=this.getDbTable(),n=e.limit??50,r=e.offset??0,i=Math.floor(Date.now()/1e3),a="WHERE 1=1",o=[];return e.queue&&(a+=" AND queue = ?",o.push(e.queue)),e.status==="waiting"?(a+=" AND reserved_at IS NULL AND available_at <= ?",o.push(i)):e.status==="active"?a+=" AND reserved_at IS NOT NULL":e.status==="delayed"&&(a+=" AND reserved_at IS NULL AND available_at > ?",o.push(i)),o.push(n,r),(await t.raw(`SELECT * FROM ${s} ${a} ORDER BY created_at DESC LIMIT ? OFFSET ?`,o)??[]).map(c=>{let v=JSON.parse(c.payload??"{}"),h=c.reserved_at!=null,I=!h&&c.available_at>i;return{id:c.id,jobClass:v.jobClass??"Unknown",queue:c.queue,status:h?"active":I?"delayed":"waiting",attempts:c.attempts??0,maxAttempts:c.max_attempts??3,payload:c.payload,createdAt:(c.created_at??0)*1e3,delayedUntil:I?c.available_at*1e3:void 0}})}async _getDatabaseJob(e){let t=await this.getDbConnection(),s=this.getDbTable(),n=await t.raw(`SELECT * FROM ${s} WHERE id = ?`,[e]);if(!n||n.length===0)return null;let r=n[0],i=JSON.parse(r.payload??"{}"),a=Math.floor(Date.now()/1e3);return{id:r.id,jobClass:i.jobClass??"Unknown",queue:r.queue,status:r.reserved_at?"active":r.available_at>a?"delayed":"waiting",attempts:r.attempts??0,maxAttempts:r.max_attempts??3,payload:r.payload,createdAt:(r.created_at??0)*1e3}}async _retryDatabaseJob(e){let t=await this.getDbConnection(),s=this.getDbTable(),n=Math.floor(Date.now()/1e3);try{return await t.raw(`UPDATE ${s} SET reserved_at = NULL, available_at = ?, attempts = 0 WHERE id = ?`,[n,e]),!0}catch{return!1}}async _deleteDatabaseJob(e){let t=await this.getDbConnection(),s=this.getDbTable();try{return await t.raw(`DELETE FROM ${s} WHERE id = ?`,[e]),!0}catch{return!1}}async _flushDatabaseFailed(e){let t=await this.getDbConnection(),s=this.getDbTable();return 0}},_=d("svelar.jobMonitor",()=>new k);L();function b(l,e,t){if(l==="*")return null;let s=new Set;for(let n of l.split(",")){let[r,i]=n.split("/"),a=i?parseInt(i,10):1;if(r==="*")for(let o=e;o<=t;o+=a)s.add(o);else if(r.includes("-")){let[o,u]=r.split("-"),c=parseInt(o,10),v=parseInt(u,10);for(let h=c;h<=v;h+=a)s.add(h)}else s.add(parseInt(r,10))}return[...s].sort((n,r)=>n-r)}function M(l){let e=l.trim().split(/\s+/);if(e.length!==5)throw new Error(`Invalid cron expression: "${l}". Expected 5 fields.`);return{minute:b(e[0],0,59),hour:b(e[1],0,23),dayOfMonth:b(e[2],1,31),month:b(e[3],1,12),dayOfWeek:b(e[4],0,6)}}function P(l,e){let t=M(l),s=e.getMinutes(),n=e.getHours(),r=e.getDate(),i=e.getMonth()+1,a=e.getDay();return!(t.minute&&!t.minute.includes(s)||t.hour&&!t.hour.includes(n)||t.dayOfMonth&&!t.dayOfMonth.includes(r)||t.month&&!t.month.includes(i)||t.dayOfWeek&&!t.dayOfWeek.includes(a))}g();var R=class{scheduler=null;startTime=new Date;taskEnabled=new Map;configure(e){this.scheduler=e,this.startTime=new Date;for(let t of e.getTasks())this.taskEnabled.has(t.name)||this.taskEnabled.set(t.name,!0)}async listTasks(){if(!this.scheduler)return[];let e=this.scheduler.getTasks(),t=await this.loadHistoryFromDb(e.map(s=>s.name));return e.map(s=>this.createTaskInfo(s,t.get(s.name)||[]))}async getTask(e){if(!this.scheduler)return null;let t=this.scheduler.getTasks().find(n=>n.name===e);if(!t)return null;let s=await this.loadHistoryFromDb([e]);return this.createTaskInfo(t,s.get(e)||[])}enableTask(e){this.taskEnabled.set(e,!0)}disableTask(e){this.taskEnabled.set(e,!1)}async runTask(e){if(!this.scheduler)throw new Error("Scheduler not configured");if(this.taskEnabled.has(e)&&!this.taskEnabled.get(e))throw new Error(`Task "${e}" is disabled`);let t=this.scheduler.getTasks().find(n=>n.name===e);if(!t)throw new Error(`Task "${e}" not found`);let s=await t.executeTask();await this.persistResult(s).catch(()=>{})}async getTaskHistory(e,t=10){return(await this.loadHistoryFromDb([e],t)).get(e)||[]}async getHealth(){if(!this.scheduler)return{totalTasks:0,enabledTasks:0,runningTasks:0,lastErrors:[],uptime:0};let e=this.scheduler.getTasks(),t=e.filter(i=>!this.taskEnabled.has(i.name)||this.taskEnabled.get(i.name)).length,s=e.filter(i=>i.isRunning()).length,n=[];try{let{Connection:i}=await Promise.resolve().then(()=>(p(),f)),a=await i.raw("SELECT task, error, ran_at FROM scheduled_task_runs WHERE error IS NOT NULL ORDER BY ran_at DESC LIMIT 10");for(let o of a)n.push({task:o.task,error:o.error,timestamp:new Date(o.ran_at)})}catch{}let r=Date.now()-this.startTime.getTime();return{totalTasks:e.length,enabledTasks:t,runningTasks:s,lastErrors:n,uptime:r}}async loadHistoryFromDb(e,t=20){let s=new Map;if(e.length===0)return s;try{let{Connection:n}=await Promise.resolve().then(()=>(p(),f)),r=e.map(()=>"?").join(", "),i=await n.raw(`SELECT task, success, duration, error, ran_at FROM scheduled_task_runs WHERE task IN (${r}) ORDER BY ran_at DESC LIMIT ?`,[...e,e.length*t]);for(let a of i){s.has(a.task)||s.set(a.task,[]);let o=s.get(a.task);o.length<t&&o.push({timestamp:new Date(a.ran_at),success:!!a.success,duration:a.duration,error:a.error||void 0})}}catch{}return s}async persistResult(e){let{Connection:t}=await Promise.resolve().then(()=>(p(),f));await t.raw("INSERT INTO scheduled_task_runs (task, success, duration, error, ran_at) VALUES (?, ?, ?, ?, ?)",[e.task,e.success,e.duration,e.error||null,e.timestamp.toISOString()])}createTaskInfo(e,t){let s=e.getExpression(),n=t.length>0?t[0]:null,r=!this.taskEnabled.has(e.name)||this.taskEnabled.get(e.name)===!0;return{name:e.name,expression:s,humanReadable:this.humanizeExpression(s),nextRun:this.getNextRun(s),lastRun:n?.timestamp,lastDuration:n?.duration,lastStatus:n?.success?"success":"failed",enabled:r,isRunning:e.isRunning(),history:t.slice(0,5)}}humanizeExpression(e){let t=e.trim().split(/\s+/);if(t.length!==5)return e;let[s,n,r,i,a]=t;if(e==="* * * * *")return"Every minute";if(e==="*/5 * * * *")return"Every 5 minutes";if(e==="*/10 * * * *")return"Every 10 minutes";if(e==="*/15 * * * *")return"Every 15 minutes";if(e==="*/30 * * * *")return"Every 30 minutes";if(e==="0 * * * *")return"Every hour";if(e==="0 0 * * *")return"Daily at midnight";if(e==="0 0 * * 0")return"Weekly (Sunday at midnight)";if(e==="0 0 1 * *")return"Monthly (1st at midnight)";if(e==="0 0 1 1 *")return"Yearly (Jan 1st at midnight)";if(r==="*"&&i==="*"&&a==="*"&&n!=="*"&&s!=="*"){let o=parseInt(n,10),u=parseInt(s,10);return`Daily at ${o.toString().padStart(2,"0")}:${u.toString().padStart(2,"0")}`}if(a==="1-5"&&r==="*"&&i==="*"&&n!=="*"&&s!=="*"){let o=parseInt(n,10),u=parseInt(s,10);return`Weekdays at ${o.toString().padStart(2,"0")}:${u.toString().padStart(2,"0")}`}return e}getNextRun(e){let t=new Date,s=new Date(t);s.setSeconds(0),s.setMilliseconds(0),s.setMinutes(s.getMinutes()+1);let n=366*24*60,r=0;for(;r<n;){if(P(e,s))return s;s.setMinutes(s.getMinutes()+1),r++}return t}},w=d("svelar.scheduleMonitor",()=>new R);var D=class extends y{name="svelar-dashboard";version="1.0.0";description="Admin dashboard with system health monitoring";dashboardConfig={enabled:!0,prefix:"/admin/dashboard",refreshInterval:5e3,sections:{logs:!0,queue:!0,scheduler:!0,system:!0}};startTime=Date.now();healthCollectionInterval=null;config(){return{key:"dashboard",defaults:this.dashboardConfig}}async register(e){e.instance("config.dashboard",this.dashboardConfig),e.instance("svelar.logViewer",m),e.instance("svelar.jobMonitor",_),e.instance("svelar.scheduleMonitor",w)}async boot(e){this.dashboardConfig.enabled&&this.dashboardConfig.refreshInterval>0&&(this.healthCollectionInterval=setInterval(()=>{this.getSystemHealth().catch(t=>{console.error("[Dashboard] Health collection error:",t)})},this.dashboardConfig.refreshInterval))}async shutdown(){this.healthCollectionInterval&&(clearInterval(this.healthCollectionInterval),this.healthCollectionInterval=null)}setConfig(e){this.dashboardConfig={...this.dashboardConfig,...e,sections:{...this.dashboardConfig.sections,...e.sections??{}}},this.healthCollectionInterval&&(clearInterval(this.healthCollectionInterval),this.healthCollectionInterval=null),this.dashboardConfig.enabled&&this.dashboardConfig.refreshInterval>0&&(this.healthCollectionInterval=setInterval(()=>{this.getSystemHealth().catch(t=>{console.error("[Dashboard] Health collection error:",t)})},this.dashboardConfig.refreshInterval))}async getSystemHealth(){let e=Date.now()-this.startTime,t=process.memoryUsage(),s=process.cpuUsage(),n=await _.getHealth(),r=await w.getHealth(),i=m.getStats(),a="healthy";return n.failureRate>50||r.lastErrors.length>5?a="critical":(n.failureRate>20||r.lastErrors.length>2)&&(a="degraded"),t.heapUsed/t.heapTotal*100>90&&(a=a==="healthy"?"degraded":a),{status:a,uptime:e,nodeVersion:process.version,memoryUsage:{heapUsed:t.heapUsed,heapTotal:t.heapTotal,rss:t.rss,external:t.external},cpuUsage:{user:s.user,system:s.system},queueHealth:n,schedulerHealth:r,logStats:i,timestamp:new Date().toISOString()}}async getDashboardData(){let e=await this.getSystemHealth(),t={health:e,config:this.dashboardConfig};return this.dashboardConfig.sections.logs&&(t.logs={stats:m.getStats(),recentErrors:m.getRecentErrors(20)}),this.dashboardConfig.sections.queue&&(t.queue={health:e.queueHealth}),this.dashboardConfig.sections.scheduler&&(t.scheduler={health:e.schedulerHealth,tasks:await w.listTasks()}),this.dashboardConfig.sections.system&&(t.system={uptime:e.uptime,nodeVersion:e.nodeVersion,memoryUsage:e.memoryUsage,cpuUsage:e.cpuUsage}),t}},S=d("svelar.dashboard",()=>new D);function ie(l){S.setConfig(l)}async function ae(){return S.getSystemHealth()}async function oe(){return S.getDashboardData()}export{S as Dashboard,ie as configureDashboard,oe as getDashboardData,ae as getDashboardHealth};
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Svelar Database Connection
|
|
3
|
+
*
|
|
4
|
+
* Manages database connections with support for SQLite, PostgreSQL, and MySQL.
|
|
5
|
+
* Wraps Drizzle ORM for the underlying driver.
|
|
6
|
+
*/
|
|
7
|
+
export type DatabaseDriver = 'sqlite' | 'postgres' | 'mysql';
|
|
8
|
+
export interface DatabaseConfig {
|
|
9
|
+
driver: DatabaseDriver;
|
|
10
|
+
/** SQLite file path */
|
|
11
|
+
filename?: string;
|
|
12
|
+
/** Host for postgres/mysql */
|
|
13
|
+
host?: string;
|
|
14
|
+
/** Port for postgres/mysql */
|
|
15
|
+
port?: number;
|
|
16
|
+
/** Database name */
|
|
17
|
+
database?: string;
|
|
18
|
+
/** Database user */
|
|
19
|
+
user?: string;
|
|
20
|
+
/** Database password */
|
|
21
|
+
password?: string;
|
|
22
|
+
/** Connection URL (alternative to individual fields) */
|
|
23
|
+
url?: string;
|
|
24
|
+
/** Enable debug/query logging */
|
|
25
|
+
debug?: boolean;
|
|
26
|
+
}
|
|
27
|
+
export interface ConnectionsConfig {
|
|
28
|
+
default: DatabaseDriver;
|
|
29
|
+
connections: Record<string, DatabaseConfig>;
|
|
30
|
+
}
|
|
31
|
+
export type DrizzleInstance = any;
|
|
32
|
+
declare class ConnectionManager {
|
|
33
|
+
private connections;
|
|
34
|
+
private config;
|
|
35
|
+
private defaultName;
|
|
36
|
+
/**
|
|
37
|
+
* Initialize the connection manager with configuration
|
|
38
|
+
*/
|
|
39
|
+
configure(config: ConnectionsConfig): void;
|
|
40
|
+
/**
|
|
41
|
+
* Get or create a database connection by name
|
|
42
|
+
*/
|
|
43
|
+
connection(name?: string): Promise<DrizzleInstance>;
|
|
44
|
+
/**
|
|
45
|
+
* Get the raw client (e.g. better-sqlite3 Database, pg Pool, mysql2 connection)
|
|
46
|
+
*/
|
|
47
|
+
rawClient(name?: string): Promise<any>;
|
|
48
|
+
/**
|
|
49
|
+
* Execute raw SQL
|
|
50
|
+
*/
|
|
51
|
+
raw(sql: string, bindings?: any[], connectionName?: string): Promise<any>;
|
|
52
|
+
/**
|
|
53
|
+
* Get the driver type for a connection
|
|
54
|
+
*/
|
|
55
|
+
getDriver(name?: string): DatabaseDriver;
|
|
56
|
+
/**
|
|
57
|
+
* Get connection config
|
|
58
|
+
*/
|
|
59
|
+
getConfig(name?: string): DatabaseConfig;
|
|
60
|
+
/**
|
|
61
|
+
* Close all connections
|
|
62
|
+
*/
|
|
63
|
+
disconnect(name?: string): Promise<void>;
|
|
64
|
+
/**
|
|
65
|
+
* Check if a connection is active
|
|
66
|
+
*/
|
|
67
|
+
isConnected(name?: string): boolean;
|
|
68
|
+
/**
|
|
69
|
+
* Run a callback inside a database transaction.
|
|
70
|
+
* Automatically commits on success, rolls back on error.
|
|
71
|
+
*/
|
|
72
|
+
transaction<T>(callback: () => Promise<T>, connectionName?: string): Promise<T>;
|
|
73
|
+
private createConnection;
|
|
74
|
+
private createSQLiteConnection;
|
|
75
|
+
private createPostgresConnection;
|
|
76
|
+
private createMySQLConnection;
|
|
77
|
+
private closeConnection;
|
|
78
|
+
}
|
|
79
|
+
export declare const Connection: ConnectionManager;
|
|
80
|
+
export {};
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Svelar Migration System
|
|
3
|
+
*
|
|
4
|
+
* Laravel-like database migrations with up/down support,
|
|
5
|
+
* batch tracking, and rollback capabilities.
|
|
6
|
+
*/
|
|
7
|
+
import { Schema } from './SchemaBuilder.js';
|
|
8
|
+
export declare abstract class Migration {
|
|
9
|
+
protected schema: Schema;
|
|
10
|
+
/** Apply the migration */
|
|
11
|
+
abstract up(): Promise<void>;
|
|
12
|
+
/** Reverse the migration */
|
|
13
|
+
abstract down(): Promise<void>;
|
|
14
|
+
}
|
|
15
|
+
export interface MigrationFile {
|
|
16
|
+
name: string;
|
|
17
|
+
timestamp: string;
|
|
18
|
+
path: string;
|
|
19
|
+
migration: Migration;
|
|
20
|
+
}
|
|
21
|
+
export declare class Migrator {
|
|
22
|
+
private migrationsTable;
|
|
23
|
+
private connectionName?;
|
|
24
|
+
constructor(connectionName?: string);
|
|
25
|
+
/**
|
|
26
|
+
* Ensure the migrations tracking table exists
|
|
27
|
+
*/
|
|
28
|
+
ensureMigrationsTable(): Promise<void>;
|
|
29
|
+
/**
|
|
30
|
+
* Run all pending migrations
|
|
31
|
+
*/
|
|
32
|
+
run(migrations: MigrationFile[]): Promise<string[]>;
|
|
33
|
+
/**
|
|
34
|
+
* Rollback the last batch of migrations
|
|
35
|
+
*/
|
|
36
|
+
rollback(migrations: MigrationFile[]): Promise<string[]>;
|
|
37
|
+
/**
|
|
38
|
+
* Reset all migrations (rollback everything)
|
|
39
|
+
*/
|
|
40
|
+
reset(migrations: MigrationFile[]): Promise<string[]>;
|
|
41
|
+
/**
|
|
42
|
+
* Refresh: reset + run all
|
|
43
|
+
*/
|
|
44
|
+
refresh(migrations: MigrationFile[]): Promise<{
|
|
45
|
+
reset: string[];
|
|
46
|
+
migrated: string[];
|
|
47
|
+
}>;
|
|
48
|
+
/**
|
|
49
|
+
* Fresh: drop ALL tables then re-run all migrations.
|
|
50
|
+
* Unlike refresh (which calls each migration's down() method),
|
|
51
|
+
* fresh simply drops every table in the database and starts clean.
|
|
52
|
+
*/
|
|
53
|
+
fresh(migrations: MigrationFile[]): Promise<{
|
|
54
|
+
dropped: string[];
|
|
55
|
+
migrated: string[];
|
|
56
|
+
}>;
|
|
57
|
+
/**
|
|
58
|
+
* Drop all tables in the database.
|
|
59
|
+
* Returns the list of dropped table names.
|
|
60
|
+
*/
|
|
61
|
+
dropAllTables(): Promise<string[]>;
|
|
62
|
+
/**
|
|
63
|
+
* Get list of already-ran migration names
|
|
64
|
+
*/
|
|
65
|
+
getRanMigrations(): Promise<string[]>;
|
|
66
|
+
/**
|
|
67
|
+
* Get the status of all migrations
|
|
68
|
+
*/
|
|
69
|
+
status(migrations: MigrationFile[]): Promise<{
|
|
70
|
+
name: string;
|
|
71
|
+
ran: boolean;
|
|
72
|
+
batch: number | null;
|
|
73
|
+
}[]>;
|
|
74
|
+
private getNextBatch;
|
|
75
|
+
private getLastBatch;
|
|
76
|
+
}
|