@razvan11/paladin 1.0.8 → 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
  */
@@ -1,8 +1,6 @@
1
1
  import { Container } from 'inversify';
2
2
  export declare const CONTAINER_KEYS: {
3
3
  readonly APP_NAME: symbol;
4
- readonly APP_DATABASE_URL: symbol;
5
- readonly APP_URL: symbol;
6
4
  readonly APP_SECRET: symbol;
7
5
  readonly APP_CWD: symbol;
8
6
  readonly APP_PUBLIC_DIR: symbol;
@@ -11,7 +9,6 @@ export declare const CONTAINER_KEYS: {
11
9
  readonly APP_IS_STAGING: symbol;
12
10
  readonly APP_IS_PRODUCTION: symbol;
13
11
  readonly APP_DEBUG: symbol;
14
- readonly APP_ENV: symbol;
15
12
  };
16
13
  declare const container: Container;
17
14
  export { container };
@@ -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,11 +18893,9 @@ 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
- APP_DATABASE_URL: Symbol.for("app.databaseUrl"),
18900
- APP_URL: Symbol.for("app.url"),
18901
18899
  APP_SECRET: Symbol.for("app.secret"),
18902
18900
  APP_CWD: Symbol.for("app.cwd"),
18903
18901
  APP_PUBLIC_DIR: Symbol.for("app.publicDir"),
@@ -18905,8 +18903,7 @@ var CONTAINER_KEYS = {
18905
18903
  APP_IS_DEVELOPMENT: Symbol.for("app.isDevelopment"),
18906
18904
  APP_IS_STAGING: Symbol.for("app.isStaging"),
18907
18905
  APP_IS_PRODUCTION: Symbol.for("app.isProduction"),
18908
- APP_DEBUG: Symbol.for("app.debug"),
18909
- APP_ENV: Symbol.for("app.env")
18906
+ APP_DEBUG: Symbol.for("app.debug")
18910
18907
  };
18911
18908
  var container = new ne({
18912
18909
  defaultScope: "Singleton"
@@ -19402,7 +19399,7 @@ var chalk = createChalk();
19402
19399
  var chalkStderr = createChalk({ level: stderrColor ? stderrColor.level : 0 });
19403
19400
  var source_default = chalk;
19404
19401
 
19405
- // src/logger/logger.ts
19402
+ // framework/logger/logger.ts
19406
19403
  var import_pretty_error = __toESM(require_PrettyError(), 1);
19407
19404
 
19408
19405
  class Logger {
@@ -19784,7 +19781,7 @@ var upgradeWebSocket = defineWebSocketHelper((c3, events) => {
19784
19781
  return;
19785
19782
  });
19786
19783
 
19787
- // src/functions/controller.ts
19784
+ // framework/functions/controller.ts
19788
19785
  var getContainer = () => globalThis.container;
19789
19786
  var CONTROLLER_METADATA = Symbol("controller:prefix");
19790
19787
  var ROUTES_METADATA = Symbol("controller:routes");
@@ -19825,7 +19822,7 @@ var del = createMethodDecorator("delete");
19825
19822
  var options = createMethodDecorator("options");
19826
19823
  var all = createMethodDecorator("all");
19827
19824
 
19828
- // src/functions/asset.ts
19825
+ // framework/functions/asset.ts
19829
19826
  var assetBasePath = "/static";
19830
19827
  function setAssetBasePath(basePath) {
19831
19828
  assetBasePath = basePath.startsWith("/") ? basePath : `/${basePath}`;
@@ -19843,8 +19840,44 @@ function publicAsset(filename) {
19843
19840
  return `${assetBasePath}/${normalizedFilename}`.replace(/\/+/g, "/");
19844
19841
  }
19845
19842
 
19846
- // src/app/app.ts
19843
+ // framework/functions/websocket.ts
19847
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;
19848
19881
 
19849
19882
  class App {
19850
19883
  app;
@@ -19853,17 +19886,18 @@ class App {
19853
19886
  validators;
19854
19887
  cors;
19855
19888
  logger;
19889
+ wsHandlers = new Map;
19890
+ server = null;
19856
19891
  constructor(options2) {
19857
19892
  this.app = new Hono2;
19858
19893
  this.name = options2.name;
19859
- this.container = getContainer2();
19894
+ this.container = getContainer3();
19860
19895
  this.validators = options2.validators;
19861
19896
  this.logger = new Logger;
19862
19897
  this.cors = options2.cors || ["*"];
19863
19898
  this.init();
19864
19899
  }
19865
19900
  init() {
19866
- console.log(this.cors);
19867
19901
  this.app.use("/*", cors({
19868
19902
  origin: this.cors,
19869
19903
  allowHeaders: ["Content-Type", "Authorization"],
@@ -19902,6 +19936,26 @@ class App {
19902
19936
  }
19903
19937
  return this;
19904
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
+ }
19905
19959
  normalizePath(prefix, path) {
19906
19960
  const normalizedPrefix = prefix.startsWith("/") ? prefix : `/${prefix}`;
19907
19961
  const normalizedPath = path.startsWith("/") ? path : `/${path}`;
@@ -19914,6 +19968,18 @@ class App {
19914
19968
  return this.app;
19915
19969
  }
19916
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
+ });
19917
19983
  try {
19918
19984
  const server = Bun.serve({
19919
19985
  port: 3000,
@@ -19921,12 +19987,29 @@ class App {
19921
19987
  development: false,
19922
19988
  fetch: this.app.fetch,
19923
19989
  websocket: {
19924
- message(ws, message) {},
19925
- open(ws) {},
19926
- close(ws, code, message) {},
19927
- 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
+ }
19928
20010
  }
19929
20011
  });
20012
+ this.server = server;
19930
20013
  this.logger.success(`Server started on port ${server.port}`);
19931
20014
  return server;
19932
20015
  } catch (error) {
@@ -19939,42 +20022,42 @@ class App {
19939
20022
  }
19940
20023
  }
19941
20024
  }
19942
- // src/functions/inject.ts
20025
+ // framework/functions/inject.ts
19943
20026
  function inject(identifier) {
19944
20027
  return U(identifier);
19945
20028
  }
19946
- // src/functions/service.ts
19947
- var getContainer3 = () => globalThis.container;
20029
+ // framework/functions/service.ts
20030
+ var getContainer4 = () => globalThis.container;
19948
20031
  function service() {
19949
20032
  return (constructor) => {
19950
20033
  W()(constructor);
19951
- const container2 = getContainer3();
20034
+ const container2 = getContainer4();
19952
20035
  if (container2 && !container2.isBound(constructor)) {
19953
20036
  container2.bind(constructor).toSelf().inSingletonScope();
19954
20037
  }
19955
20038
  return constructor;
19956
20039
  };
19957
20040
  }
19958
- // src/functions/repository.ts
19959
- var getContainer4 = () => globalThis.container;
20041
+ // framework/functions/repository.ts
20042
+ var getContainer5 = () => globalThis.container;
19960
20043
  function repository() {
19961
20044
  return (constructor) => {
19962
20045
  W()(constructor);
19963
- const container2 = getContainer4();
20046
+ const container2 = getContainer5();
19964
20047
  if (container2 && !container2.isBound(constructor)) {
19965
20048
  container2.bind(constructor).toSelf().inSingletonScope();
19966
20049
  }
19967
20050
  return constructor;
19968
20051
  };
19969
20052
  }
19970
- // src/functions/database.ts
19971
- var getContainer5 = () => globalThis.container;
20053
+ // framework/functions/database.ts
20054
+ var getContainer6 = () => globalThis.container;
19972
20055
  var databaseRegistry = new Map;
19973
20056
  function database(options2 = {}) {
19974
20057
  return (constructor) => {
19975
20058
  W()(constructor);
19976
20059
  databaseRegistry.set(constructor, options2);
19977
- const container2 = getContainer5();
20060
+ const container2 = getContainer6();
19978
20061
  if (container2 && !container2.isBound(constructor)) {
19979
20062
  container2.bind(constructor).toSelf().inSingletonScope();
19980
20063
  }
@@ -19993,12 +20076,12 @@ function getDataSourceForCLI(DatabaseClass, url) {
19993
20076
  }
19994
20077
  return source;
19995
20078
  }
19996
- // src/functions/config.ts
19997
- var getContainer6 = () => globalThis.container;
20079
+ // framework/functions/config.ts
20080
+ var getContainer7 = () => globalThis.container;
19998
20081
  function config() {
19999
20082
  return (_target) => {
20000
20083
  for (const [key, value] of Object.entries(CONTAINER_KEYS)) {
20001
- getContainer6().bind(value).toConstantValue(Bun.env[key]);
20084
+ getContainer7().bind(value).toConstantValue(Bun.env[key]);
20002
20085
  }
20003
20086
  };
20004
20087
  }
@@ -20869,14 +20952,14 @@ function validate(schemaNameOrObject, objectOrValidationOptions, maybeValidatorO
20869
20952
  }
20870
20953
  }
20871
20954
 
20872
- // src/utils/pretty-error.ts
20955
+ // framework/utils/pretty-error.ts
20873
20956
  var import_pretty_error2 = __toESM(require_PrettyError(), 1);
20874
20957
  var pe2 = new import_pretty_error2.default;
20875
20958
  pe2.skipNodeFiles();
20876
20959
  pe2.skipPackage("typescript", "bun");
20877
20960
  pe2.start();
20878
20961
 
20879
- // src/functions/validator.ts
20962
+ // framework/functions/validator.ts
20880
20963
  function validator() {
20881
20964
  return (target) => {
20882
20965
  const instance = new target;
@@ -20889,14 +20972,14 @@ function validator() {
20889
20972
  });
20890
20973
  };
20891
20974
  }
20892
- // src/functions/env.ts
20893
- var getContainer7 = () => globalThis.container;
20975
+ // framework/functions/env.ts
20976
+ var getContainer8 = () => globalThis.container;
20894
20977
  function env2() {
20895
20978
  return (target, propertyKey) => {
20896
20979
  const envVar = String(propertyKey).toUpperCase();
20897
20980
  const value = Bun.env[envVar];
20898
20981
  if (value) {
20899
- const container3 = getContainer7();
20982
+ const container3 = getContainer8();
20900
20983
  container3.bind(propertyKey).toConstantValue(value);
20901
20984
  }
20902
20985
  Object.defineProperty(target, propertyKey, {
@@ -20907,7 +20990,7 @@ function env2() {
20907
20990
  });
20908
20991
  };
20909
20992
  }
20910
- // src/functions/render.tsx
20993
+ // framework/functions/render.tsx
20911
20994
  import { renderToString } from "react-dom/server";
20912
20995
  import { jsxDEV } from "react/jsx-dev-runtime";
20913
20996
  function render(c3, Component, props) {
@@ -20927,7 +21010,7 @@ function createRenderer(c3) {
20927
21010
  renderElement: (element) => renderElement(c3, element)
20928
21011
  };
20929
21012
  }
20930
- // src/components/LayoutView.tsx
21013
+ // framework/components/LayoutView.tsx
20931
21014
  import { jsxDEV as jsxDEV2 } from "react/jsx-dev-runtime";
20932
21015
  var LayoutView = ({
20933
21016
  title,
@@ -21023,6 +21106,7 @@ var PageLoader = ({
21023
21106
  }, undefined, false, undefined, this);
21024
21107
  };
21025
21108
  export {
21109
+ websocket2 as websocket,
21026
21110
  validator,
21027
21111
  setAssetBasePath,
21028
21112
  service,
@@ -21034,6 +21118,10 @@ export {
21034
21118
  post,
21035
21119
  patch,
21036
21120
  options,
21121
+ onOpen,
21122
+ onMessage,
21123
+ onDrain,
21124
+ onClose,
21037
21125
  logger,
21038
21126
  inject,
21039
21127
  getDataSourceForCLI,
@@ -21048,6 +21136,8 @@ export {
21048
21136
  config,
21049
21137
  asset,
21050
21138
  all,
21139
+ WS_HANDLERS_METADATA,
21140
+ WEBSOCKET_METADATA,
21051
21141
  ROUTES_METADATA,
21052
21142
  PageLoader,
21053
21143
  Logger,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@razvan11/paladin",
3
- "version": "1.0.8",
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",
@@ -64,4 +64,4 @@
64
64
  "reflect-metadata": "^0.2.2",
65
65
  "zod": "^4.2.1"
66
66
  }
67
- }
67
+ }