diff --git a/.eslintrc.json b/.eslintrc.json
index 3722418..60d067f 100644
--- a/.eslintrc.json
+++ b/.eslintrc.json
@@ -1,3 +1,13 @@
{
+ "overrides": [
+ {
+ "files": [
+ "src/**/*-vm.ts"
+ ],
+ "rules": {
+ "react-hooks/rules-of-hooks": "off"
+ }
+ }
+ ],
"extends": ["next/core-web-vitals", "next/typescript"]
}
diff --git a/package.json b/package.json
index d10c385..2db330d 100644
--- a/package.json
+++ b/package.json
@@ -5,22 +5,24 @@
"scripts": {
"dev": "next dev --turbopack",
"build": "next build",
- "start": "next start",
+ "start": "next start --port 4000",
"lint": "next lint"
},
"dependencies": {
+ "next": "15.0.1",
"react": "19.0.0-rc-69d4b800-20241021",
"react-dom": "19.0.0-rc-69d4b800-20241021",
- "next": "15.0.1"
+ "reflect-metadata": "^0.2.2",
+ "tsyringe": "^4.8.0"
},
"devDependencies": {
- "typescript": "^5",
"@types/node": "^20",
"@types/react": "^18",
"@types/react-dom": "^18",
+ "eslint": "^8",
+ "eslint-config-next": "15.0.1",
"postcss": "^8",
"tailwindcss": "^3.4.1",
- "eslint": "^8",
- "eslint-config-next": "15.0.1"
+ "typescript": "^5"
}
}
diff --git a/src/app/layout.tsx b/src/app/layout.tsx
index a36cde0..1cd9211 100644
--- a/src/app/layout.tsx
+++ b/src/app/layout.tsx
@@ -1,7 +1,7 @@
+import "reflect-metadata"
import type { Metadata } from "next";
import localFont from "next/font/local";
import "./globals.css";
-
const geistSans = localFont({
src: "./fonts/GeistVF.woff",
variable: "--font-geist-sans",
diff --git a/src/app/test/page.tsx b/src/app/test/page.tsx
new file mode 100644
index 0000000..f08fc5e
--- /dev/null
+++ b/src/app/test/page.tsx
@@ -0,0 +1,8 @@
+"use client"
+import "reflect-metadata"
+import TestButtonVM from "@/app/test/vm/test-button-vm";
+import Button from "@/components/button/button";
+
+export default function Page() {
+ return
+}
\ No newline at end of file
diff --git a/src/app/test/vm/test-button-vm.ts b/src/app/test/vm/test-button-vm.ts
new file mode 100644
index 0000000..414d5ce
--- /dev/null
+++ b/src/app/test/vm/test-button-vm.ts
@@ -0,0 +1,17 @@
+import BaseVM from "@/bootstrap/helpers/vm/base-vm";
+import injectableVm from "@/bootstrap/helpers/vm/vm-decorator";
+import ButtonVm from "@/components/button/button-vm";
+
+@injectableVm()
+export default class TestButtonVM extends BaseVM {
+ useVM(): ButtonVm {
+ return {
+ props: {
+ title: "Test Button"
+ },
+ onClick: () => {
+ console.log("clicked on the button");
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/bootstrap/di/init-di.ts b/src/bootstrap/di/init-di.ts
new file mode 100644
index 0000000..79281ec
--- /dev/null
+++ b/src/bootstrap/di/init-di.ts
@@ -0,0 +1,18 @@
+import "reflect-metadata"
+
+import { container, DependencyContainer } from "tsyringe";
+
+/**
+ * Serves as a central point for initializing and configuring
+ * the DI container, ensuring that all necessary dependencies
+ * are registered and available for injection throughout the application.
+ */
+const InitDI = (): DependencyContainer => {
+ const di = container;
+
+ return di;
+};
+
+const di = InitDI();
+
+export default di;
\ No newline at end of file
diff --git a/src/bootstrap/helpers/type-helper.ts b/src/bootstrap/helpers/type-helper.ts
new file mode 100644
index 0000000..c484e86
--- /dev/null
+++ b/src/bootstrap/helpers/type-helper.ts
@@ -0,0 +1,8 @@
+declare const _: unique symbol;
+
+type Forbidden = { [_]: typeof _ };
+
+/**
+ * You can use this type to make your parent class method forbidden to overwrite
+ */
+export type NoOverride = T & Forbidden;
\ No newline at end of file
diff --git a/src/bootstrap/helpers/view/base-view.tsx b/src/bootstrap/helpers/view/base-view.tsx
new file mode 100644
index 0000000..a7498cf
--- /dev/null
+++ b/src/bootstrap/helpers/view/base-view.tsx
@@ -0,0 +1,95 @@
+"use client"
+/* eslint-disable react/display-name */
+/* eslint-disable @typescript-eslint/no-explicit-any */
+/* eslint-disable react/jsx-props-no-spreading */
+import di from "@/bootstrap/di/init-di";
+import BaseVM from "@/bootstrap/helpers/vm/base-vm";
+import { Component, ReactNode, FC, PropsWithChildren, memo, MemoExoticComponent } from "react";
+
+/* -------------------------------------------------------------------------- */
+/* Connector Component */
+/* -------------------------------------------------------------------------- */
+interface IVvmConnector extends PropsWithChildren {
+ View: FC;
+ vmName: string;
+ restProps?: PROPS;
+ memoizedByVM?: boolean;
+}
+
+/**
+ * This function is just will be used in
+ */
+const VvmConnector = memo(
+ (props: IVvmConnector) => {
+ const { View, vmName, restProps, children } = props;
+ const VmInstance = di.resolve(vmName) as new () => BaseVM;
+ if (!VmInstance) throw new Error(`Provided vm as ${vmName} is not exists`)
+
+ const vm = new VmInstance().useVM()
+
+ const allProps = {
+ restProps,
+ vm,
+ };
+
+ return {children};
+ },
+ (prevProps) => {
+ if (prevProps.memoizedByVM) return true;
+ return false;
+ },
+);
+
+/* -------------------------------------------------------------------------- */
+/* BaseView */
+/* -------------------------------------------------------------------------- */
+type IVMParent = Record;
+type IPropParent = Record | undefined;
+
+type BaseProps = {
+ vmName: string;
+ restProps?: PROPS;
+ /**
+ * By default it's true.
+ * If you pass true this view will update just by changes of vm not rest props
+ *
+ */
+ memoizedByVM?: boolean;
+ children?: ReactNode;
+};
+
+export type BuildProps<
+ IVM extends IVMParent,
+ PROPS extends IPropParent = undefined,
+> = {
+ vm: IVM;
+ restProps: PROPS;
+ children?: ReactNode;
+};
+
+export default abstract class BaseView<
+ IVM extends IVMParent,
+ PROPS extends IPropParent = undefined,
+> extends Component> {
+ /* -------------------------------- Abstracts ------------------------------- */
+ protected abstract Build(props: BuildProps): ReactNode;
+
+ /* -------------------------------- Renderer -------------------------------- */
+ render(): ReactNode {
+ const { vmName, restProps, memoizedByVM, children, ...rest } = this.props;
+
+ const Connector = VvmConnector as MemoExoticComponent<((props: IVvmConnector) => JSX.Element)>;
+
+ return (
+
+ {children}
+
+ );
+ }
+ /* -------------------------------------------------------------------------- */
+}
diff --git a/src/bootstrap/helpers/vm/base-vm.ts b/src/bootstrap/helpers/vm/base-vm.ts
new file mode 100644
index 0000000..e82a80e
--- /dev/null
+++ b/src/bootstrap/helpers/vm/base-vm.ts
@@ -0,0 +1,46 @@
+"use client"
+import { NoOverride } from "@/bootstrap/helpers/type-helper";
+import IBaseVM from "@/bootstrap/helpers/vm/i-base-vm";
+import { useState } from "react";
+
+export default abstract class BaseVM<
+ IVM,
+ DEP extends object | undefined = undefined,
+> implements IBaseVM
+{
+ /* ------------------------------ Dependencies ------------------------------ */
+ protected deps!: DEP;
+
+ /* -------------------------------- Abstracts ------------------------------- */
+ abstract useVM(): IVM;
+
+ /* -------------------------------------------------------------------------- */
+ produce(dep?: DEP) {
+ if (dep) this.deps = dep;
+
+ return this;
+ }
+
+ /* --------------------------------- Getters -------------------------------- */
+ /**
+ * You can pass your rerender method after calling useRerender on your vm
+ * so you can access to it in any method
+ */
+ protected rerender?: () => void;
+
+ /* -------------------------------------------------------------------------- */
+ /**
+ * You can use this hook in your useVm method to get rerender method
+ * @returns Rerender Method that when ever you call it you can rerender your component
+ * for showing new values
+ */
+ protected useRerender(): NoOverride<() => void> {
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ const [_, reState] = useState(false);
+
+ const rerender = () => reState((prev) => !prev);
+ return rerender as NoOverride<() => void>;
+ }
+
+ /* -------------------------------------------------------------------------- */
+}
diff --git a/src/bootstrap/helpers/vm/i-base-vm.ts b/src/bootstrap/helpers/vm/i-base-vm.ts
new file mode 100644
index 0000000..0e474fa
--- /dev/null
+++ b/src/bootstrap/helpers/vm/i-base-vm.ts
@@ -0,0 +1,3 @@
+export default interface IBaseVM {
+ useVM(): VM;
+}
diff --git a/src/bootstrap/helpers/vm/vm-decorator.ts b/src/bootstrap/helpers/vm/vm-decorator.ts
new file mode 100644
index 0000000..2092738
--- /dev/null
+++ b/src/bootstrap/helpers/vm/vm-decorator.ts
@@ -0,0 +1,9 @@
+"use client"
+import di from "@/bootstrap/di/init-di";
+import IBaseVM from "@/bootstrap/helpers/vm/i-base-vm";
+
+export default function injectableVm() {
+ return function (target: new (...args: unknown[]) => IBaseVM