@razvan11/paladin 1.0.9 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/app/app.d.ts CHANGED
@@ -4,6 +4,7 @@ import { Hono } from 'hono';
4
4
  import '../container/container';
5
5
  import type { AppConfigOptions, ValidatorType } from '../types';
6
6
  type ControllerClass = new (...args: any[]) => any;
7
+ type WebSocketClass = new (...args: any[]) => any;
7
8
  export interface StaticOptions {
8
9
  /** URL path prefix (e.g., '/static') */
9
10
  path?: string;
@@ -17,6 +18,8 @@ export declare class App {
17
18
  readonly validators?: ValidatorType;
18
19
  private cors;
19
20
  private logger;
21
+ private wsHandlers;
22
+ private server;
20
23
  constructor(options: AppConfigOptions);
21
24
  init(): void;
22
25
  /**
@@ -33,6 +36,15 @@ export declare class App {
33
36
  * Register multiple controllers at once
34
37
  */
35
38
  registerControllers(...controllers: ControllerClass[]): this;
39
+ /**
40
+ * Register a WebSocket handler with the app
41
+ * Reads handler metadata and stores event handlers
42
+ */
43
+ registerWebSocket(WSClass: WebSocketClass): this;
44
+ /**
45
+ * Register multiple WebSocket handlers at once
46
+ */
47
+ registerWebSockets(...handlers: WebSocketClass[]): this;
36
48
  /**
37
49
  * Normalize and combine path segments
38
50
  */
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,29 @@
1
+ export declare const WEBSOCKET_METADATA: unique symbol;
2
+ export declare const WS_HANDLERS_METADATA: unique symbol;
3
+ export interface WSHandlerDefinition {
4
+ event: 'message' | 'open' | 'close' | 'drain';
5
+ handlerName: string | symbol;
6
+ }
7
+ /**
8
+ * @websocket decorator
9
+ * Marks a class as a WebSocket handler and registers it with the DI container
10
+ *
11
+ * @param path - The WebSocket endpoint path (e.g., '/chat')
12
+ */
13
+ export declare function websocket(path?: string): <T extends new (...args: any[]) => any>(constructor: T) => T;
14
+ /**
15
+ * @onMessage decorator - Handles incoming WebSocket messages
16
+ */
17
+ export declare const onMessage: () => MethodDecorator;
18
+ /**
19
+ * @onOpen decorator - Handles WebSocket connection open
20
+ */
21
+ export declare const onOpen: () => MethodDecorator;
22
+ /**
23
+ * @onClose decorator - Handles WebSocket connection close
24
+ */
25
+ export declare const onClose: () => MethodDecorator;
26
+ /**
27
+ * @onDrain decorator - Handles WebSocket backpressure drain
28
+ */
29
+ export declare const onDrain: () => MethodDecorator;
package/dist/index.d.ts CHANGED
@@ -15,6 +15,7 @@ export { default as config } from './functions/config';
15
15
  export { default as validator } from './functions/validator';
16
16
  export { default as env } from './functions/env';
17
17
  export { getDataSourceForCLI } from './functions/database';
18
+ export { websocket, onMessage, onOpen, onClose, onDrain, WEBSOCKET_METADATA, WS_HANDLERS_METADATA, type WSHandlerDefinition, } from './functions/websocket';
18
19
  export { render, renderElement, createRenderer } from './functions/render';
19
20
  export { LayoutView, PageLoader, type LayoutViewProps, } from './components/LayoutView';
20
21
  export { asset, publicAsset, setAssetBasePath, getAssetBasePath, } from './functions/asset';
package/dist/index.js CHANGED
@@ -15026,10 +15026,10 @@ var require_PrettyError = __commonJS((exports, module) => {
15026
15026
  }
15027
15027
  });
15028
15028
 
15029
- // src/index.ts
15029
+ // framework/index.ts
15030
15030
  var import_reflect_metadata2 = __toESM(require_Reflect(), 1);
15031
15031
 
15032
- // src/app/app.ts
15032
+ // framework/app/app.ts
15033
15033
  var import_reflect_metadata = __toESM(require_Reflect(), 1);
15034
15034
 
15035
15035
  // node_modules/hono/dist/compose.js
@@ -18893,7 +18893,7 @@ class ne {
18893
18893
  return a3.#h.planResultCacheService.subscribe(r2), new W2(v.build(() => a3.#h.activationService), T.build(() => a3.#h.bindingService, t4), j.build(() => a3.#h.deactivationService), r2);
18894
18894
  }
18895
18895
  }
18896
- // src/container/container.ts
18896
+ // framework/container/container.ts
18897
18897
  var CONTAINER_KEYS = {
18898
18898
  APP_NAME: Symbol.for("app.name"),
18899
18899
  APP_SECRET: Symbol.for("app.secret"),
@@ -19399,7 +19399,7 @@ var chalk = createChalk();
19399
19399
  var chalkStderr = createChalk({ level: stderrColor ? stderrColor.level : 0 });
19400
19400
  var source_default = chalk;
19401
19401
 
19402
- // src/logger/logger.ts
19402
+ // framework/logger/logger.ts
19403
19403
  var import_pretty_error = __toESM(require_PrettyError(), 1);
19404
19404
 
19405
19405
  class Logger {
@@ -19781,7 +19781,7 @@ var upgradeWebSocket = defineWebSocketHelper((c3, events) => {
19781
19781
  return;
19782
19782
  });
19783
19783
 
19784
- // src/functions/controller.ts
19784
+ // framework/functions/controller.ts
19785
19785
  var getContainer = () => globalThis.container;
19786
19786
  var CONTROLLER_METADATA = Symbol("controller:prefix");
19787
19787
  var ROUTES_METADATA = Symbol("controller:routes");
@@ -19822,7 +19822,7 @@ var del = createMethodDecorator("delete");
19822
19822
  var options = createMethodDecorator("options");
19823
19823
  var all = createMethodDecorator("all");
19824
19824
 
19825
- // src/functions/asset.ts
19825
+ // framework/functions/asset.ts
19826
19826
  var assetBasePath = "/static";
19827
19827
  function setAssetBasePath(basePath) {
19828
19828
  assetBasePath = basePath.startsWith("/") ? basePath : `/${basePath}`;
@@ -19840,8 +19840,44 @@ function publicAsset(filename) {
19840
19840
  return `${assetBasePath}/${normalizedFilename}`.replace(/\/+/g, "/");
19841
19841
  }
19842
19842
 
19843
- // src/app/app.ts
19843
+ // framework/functions/websocket.ts
19844
19844
  var getContainer2 = () => globalThis.container;
19845
+ var WEBSOCKET_METADATA = Symbol("websocket:path");
19846
+ var WS_HANDLERS_METADATA = Symbol("websocket:handlers");
19847
+ function websocket2(path = "/ws") {
19848
+ return (constructor) => {
19849
+ W()(constructor);
19850
+ Reflect.defineMetadata(WEBSOCKET_METADATA, path, constructor);
19851
+ if (!Reflect.hasMetadata(WS_HANDLERS_METADATA, constructor)) {
19852
+ Reflect.defineMetadata(WS_HANDLERS_METADATA, [], constructor);
19853
+ }
19854
+ const container2 = getContainer2();
19855
+ if (container2 && !container2.isBound(constructor)) {
19856
+ container2.bind(constructor).toSelf().inSingletonScope();
19857
+ }
19858
+ return constructor;
19859
+ };
19860
+ }
19861
+ function createWSEventDecorator(event) {
19862
+ return () => {
19863
+ return (target, propertyKey, _descriptor) => {
19864
+ const constructor = target.constructor;
19865
+ const handlers = Reflect.getMetadata(WS_HANDLERS_METADATA, constructor) || [];
19866
+ handlers.push({
19867
+ event,
19868
+ handlerName: propertyKey
19869
+ });
19870
+ Reflect.defineMetadata(WS_HANDLERS_METADATA, handlers, constructor);
19871
+ };
19872
+ };
19873
+ }
19874
+ var onMessage = createWSEventDecorator("message");
19875
+ var onOpen = createWSEventDecorator("open");
19876
+ var onClose = createWSEventDecorator("close");
19877
+ var onDrain = createWSEventDecorator("drain");
19878
+
19879
+ // framework/app/app.ts
19880
+ var getContainer3 = () => globalThis.container;
19845
19881
 
19846
19882
  class App {
19847
19883
  app;
@@ -19850,17 +19886,18 @@ class App {
19850
19886
  validators;
19851
19887
  cors;
19852
19888
  logger;
19889
+ wsHandlers = new Map;
19890
+ server = null;
19853
19891
  constructor(options2) {
19854
19892
  this.app = new Hono2;
19855
19893
  this.name = options2.name;
19856
- this.container = getContainer2();
19894
+ this.container = getContainer3();
19857
19895
  this.validators = options2.validators;
19858
19896
  this.logger = new Logger;
19859
19897
  this.cors = options2.cors || ["*"];
19860
19898
  this.init();
19861
19899
  }
19862
19900
  init() {
19863
- console.log(this.cors);
19864
19901
  this.app.use("/*", cors({
19865
19902
  origin: this.cors,
19866
19903
  allowHeaders: ["Content-Type", "Authorization"],
@@ -19899,6 +19936,26 @@ class App {
19899
19936
  }
19900
19937
  return this;
19901
19938
  }
19939
+ registerWebSocket(WSClass) {
19940
+ const path = Reflect.getMetadata(WEBSOCKET_METADATA, WSClass) || "/ws";
19941
+ const handlers = Reflect.getMetadata(WS_HANDLERS_METADATA, WSClass) || [];
19942
+ const instance = this.container.get(WSClass);
19943
+ const handlerMap = {};
19944
+ for (const handler of handlers) {
19945
+ handlerMap[handler.event] = instance[handler.handlerName].bind(instance);
19946
+ }
19947
+ this.wsHandlers.set(path, handlerMap);
19948
+ if (Bun.env.APP_ENV === "local") {
19949
+ this.logger.info(` WS ${path}`);
19950
+ }
19951
+ return this;
19952
+ }
19953
+ registerWebSockets(...handlers) {
19954
+ for (const handler of handlers) {
19955
+ this.registerWebSocket(handler);
19956
+ }
19957
+ return this;
19958
+ }
19902
19959
  normalizePath(prefix, path) {
19903
19960
  const normalizedPrefix = prefix.startsWith("/") ? prefix : `/${prefix}`;
19904
19961
  const normalizedPath = path.startsWith("/") ? path : `/${path}`;
@@ -19911,19 +19968,54 @@ class App {
19911
19968
  return this.app;
19912
19969
  }
19913
19970
  async run() {
19971
+ const self2 = this;
19972
+ const honoFetch = this.app.fetch.bind(this.app);
19973
+ const wsPaths = new Set;
19974
+ this.wsHandlers.forEach((_3, path) => {
19975
+ wsPaths.add(path);
19976
+ });
19914
19977
  try {
19915
19978
  const server = Bun.serve({
19916
19979
  port: 3000,
19917
19980
  hostname: "localhost",
19918
19981
  development: false,
19919
- fetch: this.app.fetch,
19982
+ fetch(req, server2) {
19983
+ const url = new URL(req.url);
19984
+ if (wsPaths.has(url.pathname)) {
19985
+ const upgraded = server2.upgrade(req, {
19986
+ data: { path: url.pathname }
19987
+ });
19988
+ if (upgraded) {
19989
+ return;
19990
+ }
19991
+ return new Response("WebSocket upgrade failed", { status: 400 });
19992
+ }
19993
+ return honoFetch(req, server2);
19994
+ },
19920
19995
  websocket: {
19921
- message(ws, message) {},
19922
- open(ws) {},
19923
- close(ws, code, message) {},
19924
- drain(ws) {}
19996
+ message(ws, message) {
19997
+ const wsPath = ws.data?.path;
19998
+ const handlers = self2.wsHandlers.get(wsPath);
19999
+ handlers?.message?.(ws, message);
20000
+ },
20001
+ open(ws) {
20002
+ const wsPath = ws.data?.path;
20003
+ const handlers = self2.wsHandlers.get(wsPath);
20004
+ handlers?.open?.(ws);
20005
+ },
20006
+ close(ws, code, message) {
20007
+ const wsPath = ws.data?.path;
20008
+ const handlers = self2.wsHandlers.get(wsPath);
20009
+ handlers?.close?.(ws, code, message);
20010
+ },
20011
+ drain(ws) {
20012
+ const wsPath = ws.data?.path;
20013
+ const handlers = self2.wsHandlers.get(wsPath);
20014
+ handlers?.drain?.(ws);
20015
+ }
19925
20016
  }
19926
20017
  });
20018
+ this.server = server;
19927
20019
  this.logger.success(`Server started on port ${server.port}`);
19928
20020
  return server;
19929
20021
  } catch (error) {
@@ -19936,42 +20028,42 @@ class App {
19936
20028
  }
19937
20029
  }
19938
20030
  }
19939
- // src/functions/inject.ts
20031
+ // framework/functions/inject.ts
19940
20032
  function inject(identifier) {
19941
20033
  return U(identifier);
19942
20034
  }
19943
- // src/functions/service.ts
19944
- var getContainer3 = () => globalThis.container;
20035
+ // framework/functions/service.ts
20036
+ var getContainer4 = () => globalThis.container;
19945
20037
  function service() {
19946
20038
  return (constructor) => {
19947
20039
  W()(constructor);
19948
- const container2 = getContainer3();
20040
+ const container2 = getContainer4();
19949
20041
  if (container2 && !container2.isBound(constructor)) {
19950
20042
  container2.bind(constructor).toSelf().inSingletonScope();
19951
20043
  }
19952
20044
  return constructor;
19953
20045
  };
19954
20046
  }
19955
- // src/functions/repository.ts
19956
- var getContainer4 = () => globalThis.container;
20047
+ // framework/functions/repository.ts
20048
+ var getContainer5 = () => globalThis.container;
19957
20049
  function repository() {
19958
20050
  return (constructor) => {
19959
20051
  W()(constructor);
19960
- const container2 = getContainer4();
20052
+ const container2 = getContainer5();
19961
20053
  if (container2 && !container2.isBound(constructor)) {
19962
20054
  container2.bind(constructor).toSelf().inSingletonScope();
19963
20055
  }
19964
20056
  return constructor;
19965
20057
  };
19966
20058
  }
19967
- // src/functions/database.ts
19968
- var getContainer5 = () => globalThis.container;
20059
+ // framework/functions/database.ts
20060
+ var getContainer6 = () => globalThis.container;
19969
20061
  var databaseRegistry = new Map;
19970
20062
  function database(options2 = {}) {
19971
20063
  return (constructor) => {
19972
20064
  W()(constructor);
19973
20065
  databaseRegistry.set(constructor, options2);
19974
- const container2 = getContainer5();
20066
+ const container2 = getContainer6();
19975
20067
  if (container2 && !container2.isBound(constructor)) {
19976
20068
  container2.bind(constructor).toSelf().inSingletonScope();
19977
20069
  }
@@ -19990,12 +20082,12 @@ function getDataSourceForCLI(DatabaseClass, url) {
19990
20082
  }
19991
20083
  return source;
19992
20084
  }
19993
- // src/functions/config.ts
19994
- var getContainer6 = () => globalThis.container;
20085
+ // framework/functions/config.ts
20086
+ var getContainer7 = () => globalThis.container;
19995
20087
  function config() {
19996
20088
  return (_target) => {
19997
20089
  for (const [key, value] of Object.entries(CONTAINER_KEYS)) {
19998
- getContainer6().bind(value).toConstantValue(Bun.env[key]);
20090
+ getContainer7().bind(value).toConstantValue(Bun.env[key]);
19999
20091
  }
20000
20092
  };
20001
20093
  }
@@ -20866,14 +20958,14 @@ function validate(schemaNameOrObject, objectOrValidationOptions, maybeValidatorO
20866
20958
  }
20867
20959
  }
20868
20960
 
20869
- // src/utils/pretty-error.ts
20961
+ // framework/utils/pretty-error.ts
20870
20962
  var import_pretty_error2 = __toESM(require_PrettyError(), 1);
20871
20963
  var pe2 = new import_pretty_error2.default;
20872
20964
  pe2.skipNodeFiles();
20873
20965
  pe2.skipPackage("typescript", "bun");
20874
20966
  pe2.start();
20875
20967
 
20876
- // src/functions/validator.ts
20968
+ // framework/functions/validator.ts
20877
20969
  function validator() {
20878
20970
  return (target) => {
20879
20971
  const instance = new target;
@@ -20886,14 +20978,14 @@ function validator() {
20886
20978
  });
20887
20979
  };
20888
20980
  }
20889
- // src/functions/env.ts
20890
- var getContainer7 = () => globalThis.container;
20981
+ // framework/functions/env.ts
20982
+ var getContainer8 = () => globalThis.container;
20891
20983
  function env2() {
20892
20984
  return (target, propertyKey) => {
20893
20985
  const envVar = String(propertyKey).toUpperCase();
20894
20986
  const value = Bun.env[envVar];
20895
20987
  if (value) {
20896
- const container3 = getContainer7();
20988
+ const container3 = getContainer8();
20897
20989
  container3.bind(propertyKey).toConstantValue(value);
20898
20990
  }
20899
20991
  Object.defineProperty(target, propertyKey, {
@@ -20904,7 +20996,7 @@ function env2() {
20904
20996
  });
20905
20997
  };
20906
20998
  }
20907
- // src/functions/render.tsx
20999
+ // framework/functions/render.tsx
20908
21000
  import { renderToString } from "react-dom/server";
20909
21001
  import { jsxDEV } from "react/jsx-dev-runtime";
20910
21002
  function render(c3, Component, props) {
@@ -20924,7 +21016,7 @@ function createRenderer(c3) {
20924
21016
  renderElement: (element) => renderElement(c3, element)
20925
21017
  };
20926
21018
  }
20927
- // src/components/LayoutView.tsx
21019
+ // framework/components/LayoutView.tsx
20928
21020
  import { jsxDEV as jsxDEV2 } from "react/jsx-dev-runtime";
20929
21021
  var LayoutView = ({
20930
21022
  title,
@@ -21020,6 +21112,7 @@ var PageLoader = ({
21020
21112
  }, undefined, false, undefined, this);
21021
21113
  };
21022
21114
  export {
21115
+ websocket2 as websocket,
21023
21116
  validator,
21024
21117
  setAssetBasePath,
21025
21118
  service,
@@ -21031,6 +21124,10 @@ export {
21031
21124
  post,
21032
21125
  patch,
21033
21126
  options,
21127
+ onOpen,
21128
+ onMessage,
21129
+ onDrain,
21130
+ onClose,
21034
21131
  logger,
21035
21132
  inject,
21036
21133
  getDataSourceForCLI,
@@ -21045,6 +21142,8 @@ export {
21045
21142
  config,
21046
21143
  asset,
21047
21144
  all,
21145
+ WS_HANDLERS_METADATA,
21146
+ WEBSOCKET_METADATA,
21048
21147
  ROUTES_METADATA,
21049
21148
  PageLoader,
21050
21149
  Logger,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@razvan11/paladin",
3
- "version": "1.0.9",
3
+ "version": "1.1.1",
4
4
  "description": "A Bun-based backend framework with decorators, dependency injection, and controller registration",
5
5
  "keywords": [
6
6
  "bun",
@@ -32,7 +32,7 @@
32
32
  ],
33
33
  "scripts": {
34
34
  "dev": "bun run ./example/index.ts",
35
- "build": " bun build ./src/index.ts --outdir ./dist --target bun --external react --external react-dom && bun run build:types",
35
+ "build": "bun build ./framework/index.ts --outdir ./dist --target bun --external react --external react-dom && bun run build:types",
36
36
  "build:types": "tsc -p tsconfig.build.json",
37
37
  "prepublishOnly": "bun run build",
38
38
  "fmt": "bunx @biomejs/biome format --write",
@@ -64,4 +64,4 @@
64
64
  "reflect-metadata": "^0.2.2",
65
65
  "zod": "^4.2.1"
66
66
  }
67
- }
67
+ }