diff --git a/package.json b/package.json index e6afe3f..7de4001 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,7 @@ "clean": "rm -rf dist", "build": "bun run clean && bun build:types && bun build:bundle", "build:types": "tsc --emitDeclarationOnly", - "build:bundle": "bun build src/index.ts src/decorators/index.ts src/testing/index.ts --outdir dist --target bun --format esm", + "build:bundle": "bun build src/index.ts src/decorators/index.ts src/testing/index.ts --outdir dist --target bun --format esm --splitting", "test": "bun test", "test:watch": "bun test --watch", "test:coverage": "bun test --coverage", diff --git a/tests/decorators.test.ts b/tests/decorators.test.ts index 5dc9234..3d10ccc 100644 --- a/tests/decorators.test.ts +++ b/tests/decorators.test.ts @@ -1,6 +1,12 @@ import { describe, test, expect, beforeEach } from "bun:test"; import { Container, Lifetime } from "../src/index.js"; -import { injectable, singleton, transient, scoped, inject } from "../src/decorators/index.js"; +import { + injectable, + singleton, + transient, + scoped, + inject, +} from "../src/decorators/index.js"; describe("Decorator Integration", () => { let container: Container; @@ -193,6 +199,27 @@ describe("Decorator Integration", () => { }); describe("@inject decorator", () => { + test("should inject an explicit token when imported from the decorator subpath", () => { + @singleton() + class Database { + query() { + return "data"; + } + } + + @singleton() + class UserService { + constructor(@inject(Database) public db: Database) {} + } + + container.register(Database); + container.register(UserService); + + const service = container.resolve(UserService); + expect(service.db).toBeInstanceOf(Database); + expect(service.db.query()).toBe("data"); + }); + test("should inject explicit tokens for interfaces using factory", () => { const ILogger = Symbol("ILogger"); const IDatabase = Symbol("IDatabase"); @@ -308,7 +335,11 @@ describe("Decorator Integration", () => { container.register(Logger); container.register(Database); container.registerFactory(ComplexService, (c) => { - return new ComplexService(c.resolve(Config), c.resolve(Logger), c.resolve(Database)); + return new ComplexService( + c.resolve(Config), + c.resolve(Logger), + c.resolve(Database), + ); }); const service = container.resolve(ComplexService); @@ -334,9 +365,13 @@ describe("Decorator Integration", () => { container.register(ConsoleLogger); // Factory needs explicit lifetime; decorator metadata applies only to register() - container.registerFactory(UserService, (c) => new UserService(c.resolve(ConsoleLogger)), { - lifetime: Lifetime.Singleton, - }); + container.registerFactory( + UserService, + (c) => new UserService(c.resolve(ConsoleLogger)), + { + lifetime: Lifetime.Singleton, + }, + ); const service1 = container.resolve(UserService); const service2 = container.resolve(UserService); diff --git a/tests/package.test.ts b/tests/package.test.ts new file mode 100644 index 0000000..913406e --- /dev/null +++ b/tests/package.test.ts @@ -0,0 +1,64 @@ +import { describe, test } from "bun:test"; +import { cp, mkdtemp, mkdir, rm, symlink, writeFile } from "node:fs/promises"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; + +const projectRoot = join(import.meta.dir, ".."); + +describe("Package build", () => { + test("shares decorator metadata between package entrypoints", async () => { + const packageRoot = await mkdtemp( + join(tmpdir(), "fast-injection-package-"), + ); + const consumerRoot = await mkdtemp( + join(tmpdir(), "fast-injection-consumer-"), + ); + + try { + await cp( + join(projectRoot, "package.json"), + join(packageRoot, "package.json"), + ); + await Bun.$`bun build ${join(projectRoot, "src/index.ts")} ${join(projectRoot, "src/decorators/index.ts")} ${join(projectRoot, "src/testing/index.ts")} --outdir ${join(packageRoot, "dist")} --target bun --format esm --splitting`.quiet(); + await mkdir(join(consumerRoot, "node_modules")); + await symlink( + packageRoot, + join(consumerRoot, "node_modules", "fast-injection"), + ); + await writeFile( + join(consumerRoot, "tsconfig.json"), + JSON.stringify({ compilerOptions: { experimentalDecorators: true } }), + ); + await writeFile( + join(consumerRoot, "index.ts"), + ` +import { Container } from "fast-injection"; +import { inject, singleton } from "fast-injection/decorators"; + +@singleton() +class Database {} + +@singleton() +class UserService { + constructor(@inject(Database) readonly database: Database) {} +} + +const container = new Container(); +container.register(Database); +container.register(UserService); + +if (!(container.resolve(UserService).database instanceof Database)) { + throw new Error("Decorator metadata was not shared between package entrypoints"); +} +`, + ); + + await Bun.$`bun index.ts`.cwd(consumerRoot).quiet(); + } finally { + await Promise.all([ + rm(packageRoot, { force: true, recursive: true }), + rm(consumerRoot, { force: true, recursive: true }), + ]); + } + }); +}); diff --git a/tsconfig.json b/tsconfig.json index ff3d1a2..29cce41 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -22,6 +22,7 @@ "declaration": true, "declarationMap": true, "sourceMap": true, + "rootDir": "./src", "outDir": "./dist", "removeComments": true, "noEmit": false,