@razvan11/paladin 1.0.9 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/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,6 +19968,18 @@ class App {
19911
19968
  return this.app;
19912
19969
  }
19913
19970
  async run() {
19971
+ const self2 = this;
19972
+ this.wsHandlers.forEach((_3, path) => {
19973
+ this.app.get(path, (c3) => {
19974
+ const upgraded = self2.server?.upgrade(c3.req.raw, {
19975
+ data: { path }
19976
+ });
19977
+ if (upgraded) {
19978
+ return new Response(null, { status: 101 });
19979
+ }
19980
+ return c3.text("WebSocket upgrade failed", 400);
19981
+ });
19982
+ });
19914
19983
  try {
19915
19984
  const server = Bun.serve({
19916
19985
  port: 3000,
@@ -19918,12 +19987,29 @@ class App {
19918
19987
  development: false,
19919
19988
  fetch: this.app.fetch,
19920
19989
  websocket: {
19921
- message(ws, message) {},
19922
- open(ws) {},
19923
- close(ws, code, message) {},
19924
- drain(ws) {}
19990
+ message(ws, message) {
19991
+ const wsPath = ws.data?.path;
19992
+ const handlers = self2.wsHandlers.get(wsPath);
19993
+ handlers?.message?.(ws, message);
19994
+ },
19995
+ open(ws) {
19996
+ const wsPath = ws.data?.path;
19997
+ const handlers = self2.wsHandlers.get(wsPath);
19998
+ handlers?.open?.(ws);
19999
+ },
20000
+ close(ws, code, message) {
20001
+ const wsPath = ws.data?.path;
20002
+ const handlers = self2.wsHandlers.get(wsPath);
20003
+ handlers?.close?.(ws, code, message);
20004
+ },
20005
+ drain(ws) {
20006
+ const wsPath = ws.data?.path;
20007
+ const handlers = self2.wsHandlers.get(wsPath);
20008
+ handlers?.drain?.(ws);
20009
+ }
19925
20010
  }
19926
20011
  });
20012
+ this.server = server;
19927
20013
  this.logger.success(`Server started on port ${server.port}`);
19928
20014
  return server;
19929
20015
  } catch (error) {
@@ -19936,42 +20022,42 @@ class App {
19936
20022
  }
19937
20023
  }
19938
20024
  }
19939
- // src/functions/inject.ts
20025
+ // framework/functions/inject.ts
19940
20026
  function inject(identifier) {
19941
20027
  return U(identifier);
19942
20028
  }
19943
- // src/functions/service.ts
19944
- var getContainer3 = () => globalThis.container;
20029
+ // framework/functions/service.ts
20030
+ var getContainer4 = () => globalThis.container;
19945
20031
  function service() {
19946
20032
  return (constructor) => {
19947
20033
  W()(constructor);
19948
- const container2 = getContainer3();
20034
+ const container2 = getContainer4();
19949
20035
  if (container2 && !container2.isBound(constructor)) {
19950
20036
  container2.bind(constructor).toSelf().inSingletonScope();
19951
20037
  }
19952
20038
  return constructor;
19953
20039
  };
19954
20040
  }
19955
- // src/functions/repository.ts
19956
- var getContainer4 = () => globalThis.container;
20041
+ // framework/functions/repository.ts
20042
+ var getContainer5 = () => globalThis.container;
19957
20043
  function repository() {
19958
20044
  return (constructor) => {
19959
20045
  W()(constructor);
19960
- const container2 = getContainer4();
20046
+ const container2 = getContainer5();
19961
20047
  if (container2 && !container2.isBound(constructor)) {
19962
20048
  container2.bind(constructor).toSelf().inSingletonScope();
19963
20049
  }
19964
20050
  return constructor;
19965
20051
  };
19966
20052
  }
19967
- // src/functions/database.ts
19968
- var getContainer5 = () => globalThis.container;
20053
+ // framework/functions/database.ts
20054
+ var getContainer6 = () => globalThis.container;
19969
20055
  var databaseRegistry = new Map;
19970
20056
  function database(options2 = {}) {
19971
20057
  return (constructor) => {
19972
20058
  W()(constructor);
19973
20059
  databaseRegistry.set(constructor, options2);
19974
- const container2 = getContainer5();
20060
+ const container2 = getContainer6();
19975
20061
  if (container2 && !container2.isBound(constructor)) {
19976
20062
  container2.bind(constructor).toSelf().inSingletonScope();
19977
20063
  }
@@ -19990,12 +20076,12 @@ function getDataSourceForCLI(DatabaseClass, url) {
19990
20076
  }
19991
20077
  return source;
19992
20078
  }
19993
- // src/functions/config.ts
19994
- var getContainer6 = () => globalThis.container;
20079
+ // framework/functions/config.ts
20080
+ var getContainer7 = () => globalThis.container;
19995
20081
  function config() {
19996
20082
  return (_target) => {
19997
20083
  for (const [key, value] of Object.entries(CONTAINER_KEYS)) {
19998
- getContainer6().bind(value).toConstantValue(Bun.env[key]);
20084
+ getContainer7().bind(value).toConstantValue(Bun.env[key]);
19999
20085
  }
20000
20086
  };
20001
20087
  }
@@ -20866,14 +20952,14 @@ function validate(schemaNameOrObject, objectOrValidationOptions, maybeValidatorO
20866
20952
  }
20867
20953
  }
20868
20954
 
20869
- // src/utils/pretty-error.ts
20955
+ // framework/utils/pretty-error.ts
20870
20956
  var import_pretty_error2 = __toESM(require_PrettyError(), 1);
20871
20957
  var pe2 = new import_pretty_error2.default;
20872
20958
  pe2.skipNodeFiles();
20873
20959
  pe2.skipPackage("typescript", "bun");
20874
20960
  pe2.start();
20875
20961
 
20876
- // src/functions/validator.ts
20962
+ // framework/functions/validator.ts
20877
20963
  function validator() {
20878
20964
  return (target) => {
20879
20965
  const instance = new target;
@@ -20886,14 +20972,14 @@ function validator() {
20886
20972
  });
20887
20973
  };
20888
20974
  }
20889
- // src/functions/env.ts
20890
- var getContainer7 = () => globalThis.container;
20975
+ // framework/functions/env.ts
20976
+ var getContainer8 = () => globalThis.container;
20891
20977
  function env2() {
20892
20978
  return (target, propertyKey) => {
20893
20979
  const envVar = String(propertyKey).toUpperCase();
20894
20980
  const value = Bun.env[envVar];
20895
20981
  if (value) {
20896
- const container3 = getContainer7();
20982
+ const container3 = getContainer8();
20897
20983
  container3.bind(propertyKey).toConstantValue(value);
20898
20984
  }
20899
20985
  Object.defineProperty(target, propertyKey, {
@@ -20904,7 +20990,7 @@ function env2() {
20904
20990
  });
20905
20991
  };
20906
20992
  }
20907
- // src/functions/render.tsx
20993
+ // framework/functions/render.tsx
20908
20994
  import { renderToString } from "react-dom/server";
20909
20995
  import { jsxDEV } from "react/jsx-dev-runtime";
20910
20996
  function render(c3, Component, props) {
@@ -20924,7 +21010,7 @@ function createRenderer(c3) {
20924
21010
  renderElement: (element) => renderElement(c3, element)
20925
21011
  };
20926
21012
  }
20927
- // src/components/LayoutView.tsx
21013
+ // framework/components/LayoutView.tsx
20928
21014
  import { jsxDEV as jsxDEV2 } from "react/jsx-dev-runtime";
20929
21015
  var LayoutView = ({
20930
21016
  title,
@@ -21020,6 +21106,7 @@ var PageLoader = ({
21020
21106
  }, undefined, false, undefined, this);
21021
21107
  };
21022
21108
  export {
21109
+ websocket2 as websocket,
21023
21110
  validator,
21024
21111
  setAssetBasePath,
21025
21112
  service,
@@ -21031,6 +21118,10 @@ export {
21031
21118
  post,
21032
21119
  patch,
21033
21120
  options,
21121
+ onOpen,
21122
+ onMessage,
21123
+ onDrain,
21124
+ onClose,
21034
21125
  logger,
21035
21126
  inject,
21036
21127
  getDataSourceForCLI,
@@ -21045,6 +21136,8 @@ export {
21045
21136
  config,
21046
21137
  asset,
21047
21138
  all,
21139
+ WS_HANDLERS_METADATA,
21140
+ WEBSOCKET_METADATA,
21048
21141
  ROUTES_METADATA,
21049
21142
  PageLoader,
21050
21143
  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.0",
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",