diff --git a/AGENTS.md b/AGENTS.md index 2316b63..e90af20 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -32,6 +32,11 @@ Keep implementation, runtime tests, and type tests close together in `src/`. When adding a public symbol, export it through the nearest barrel file and make sure it is reachable from `src/index.ts`. +The folder barrel files intentionally enumerate the public API. Do not replace +their named exports with `export *`: registration, resolution, identity, cache, +and builder implementations must remain internal. Internal code should import +those symbols from their defining files rather than from a public barrel. + ## Runtime model and invariants - A module registers, in order: hooks, installers, controllers, then child diff --git a/package-lock.json b/package-lock.json index f5ff126..1134a2b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,12 +9,12 @@ "version": "0.2.0", "license": "MIT", "dependencies": { + "@sinclair/typebox": "^0.34.49", "fastify": "^5.6.1", "fastify-plugin": "^5.0.0" }, "devDependencies": { "@eslint/js": "^9.35.0", - "@sinclair/typebox": "^0.34.41", "@types/node": "^24.5.2", "c8": "^10.1.3", "eslint": "^9.35.0", @@ -1318,7 +1318,6 @@ "version": "0.34.41", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.41.tgz", "integrity": "sha512-6gS8pZzSXdyRHTIqoqSVknxolr1kzfy4/CeDnrzsVz8TTIWUbOBr6gnzOmTYJ3eXQNh4IYHIGi5aIL7sOZ2G/g==", - "dev": true, "license": "MIT" }, "node_modules/@tsd/typescript": { diff --git a/package.json b/package.json index 380eac1..b9025f0 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,6 @@ "license": "MIT", "devDependencies": { "@eslint/js": "^9.39.4", - "@sinclair/typebox": "^0.34.49", "@types/node": "^25.9.4", "c8": "^10.1.3", "eslint": "^9.39.4", @@ -51,6 +50,7 @@ "typescript-eslint": "^8.62.0" }, "dependencies": { + "@sinclair/typebox": "^0.34.49", "fastify": "^5.8.5", "fastify-plugin": "^5.1.0" }, diff --git a/src/container/container.ts b/src/container/container.ts index bd55ba5..bf4e609 100644 --- a/src/container/container.ts +++ b/src/container/container.ts @@ -1,10 +1,10 @@ -import { ModuleContext } from "../modules"; +import type { ModuleContext } from "../modules/module.types"; import type { DepValues, ProviderAny, ProviderDef, BaseProviderDepsMap, -} from "../providers"; +} from "../providers/providers.types"; export class Container { private singletons = new WeakMap>(); diff --git a/src/controllers/controllers.ts b/src/controllers/controllers.ts index df5feb1..45e5c96 100644 --- a/src/controllers/controllers.ts +++ b/src/controllers/controllers.ts @@ -1,11 +1,9 @@ -import type { ProvidersMap } from "../providers"; +import type { ProvidersMap } from "../providers/providers.types"; import { RoutesBuilder } from "./routes-builder"; -import { - ControllerConfig, - ControllerOptions, - resolveProviderMap, -} from "../modules"; -import { AdapterMap, resolveAdapterMap } from "../fastify"; +import type { ControllerConfig, ControllerOptions } from "./controllers.types"; +import { resolveProviderMap } from "../modules/module"; +import { resolveAdapterMap } from "../fastify/adapters"; +import type { AdapterMap } from "../fastify/adapters.types"; export function createController< Providers extends ProvidersMap, @@ -46,5 +44,3 @@ export function createController< }, }; } - -export const controller = createController; diff --git a/src/controllers/controllers.types.ts b/src/controllers/controllers.types.ts index 9c98a48..cd95d9d 100644 --- a/src/controllers/controllers.types.ts +++ b/src/controllers/controllers.types.ts @@ -20,8 +20,12 @@ import { Static, TSchema } from "@sinclair/typebox"; import { ExposeDeps, ProvidersMap } from "../providers"; import { RoutesBuilder } from "./routes-builder"; import { Container } from "../container/container"; -import { AdapterCache, AdapterMap, AdapterValues } from "../fastify"; -import { ModuleContext } from "../modules"; +import type { + AdapterCache, + AdapterMap, + AdapterValues, +} from "../fastify/adapters.types"; +import type { ModuleContext } from "../modules/module.types"; type ErrorHandler = ( error: unknown, diff --git a/src/controllers/index.ts b/src/controllers/index.ts index 15e8978..877b283 100644 --- a/src/controllers/index.ts +++ b/src/controllers/index.ts @@ -1,3 +1,12 @@ -export * from "./routes-builder"; -export * from "./controllers"; -export * from "./controllers.types"; +export { + createController, + createController as controller, +} from "./controllers"; +export type { + BaseStratifyRouteOptions, + ControllerBuilderCallback, + ControllerConfig, + ControllerOptions, + ExtractRouteGenerics, + StratifyRouteOptions, +} from "./controllers.types"; diff --git a/src/fastify/adapter-fastify-instance.ts b/src/fastify/adapter-fastify-instance.ts index 0093c73..a0ec46b 100644 --- a/src/fastify/adapter-fastify-instance.ts +++ b/src/fastify/adapter-fastify-instance.ts @@ -51,11 +51,11 @@ const forbiddenAdapterFastifyProperties = [ "addHttpMethod", "addConstraintStrategy", "supportedMethods", - Symbol.asyncDispose, ] as const; type ForbiddenAdapterFastifyProperty = - (typeof forbiddenAdapterFastifyProperties)[number]; + | (typeof forbiddenAdapterFastifyProperties)[number] + | Extract; /** * Read-only Fastify view available to adapters. @@ -68,9 +68,10 @@ export type AdapterFastifyInstance = Readonly< Omit >; -const forbiddenProperties = new Set( - forbiddenAdapterFastifyProperties, -); +const forbiddenProperties = new Set([ + ...forbiddenAdapterFastifyProperties, + Symbol.asyncDispose, +]); export function restrictFastifyForAdapter( fastify: FastifyInstance, diff --git a/src/fastify/adapters.ts b/src/fastify/adapters.ts index b5109f3..6f1cefb 100644 --- a/src/fastify/adapters.ts +++ b/src/fastify/adapters.ts @@ -45,5 +45,3 @@ export async function resolveAdapterMap( return out as AdapterValues; } - -export const adapter = createAdapter; diff --git a/src/fastify/index.ts b/src/fastify/index.ts index 577997d..254eabd 100644 --- a/src/fastify/index.ts +++ b/src/fastify/index.ts @@ -1,4 +1,15 @@ -export * from "./installers"; -export * from "./installers.types"; -export * from "./adapters"; -export * from "./adapters.types"; +export { createAdapter, createAdapter as adapter } from "./adapters"; +export { createInstaller, createInstaller as installer } from "./installers"; +export type { + AdapterAny, + AdapterDef, + AdapterFastifyInstance, + AdapterMap, + AdapterOptions, + AdapterValues, +} from "./adapters.types"; +export type { + InstallerBuilderCallback, + InstallerConfig, + InstallerOptions, +} from "./installers.types"; diff --git a/src/fastify/installers.ts b/src/fastify/installers.ts index 3a798c8..3806003 100644 --- a/src/fastify/installers.ts +++ b/src/fastify/installers.ts @@ -1,7 +1,8 @@ import type { FastifyInstance } from "fastify"; -import type { ProvidersMap } from "../../src/providers"; +import type { ProvidersMap } from "../providers/providers.types"; import type { Container } from "../../src/container/container"; -import { resolveProviderMap, type ModuleContext } from "../../src/modules"; +import { resolveProviderMap } from "../modules/module"; +import type { ModuleContext } from "../modules/module.types"; import type { InstallerOptions, InstallerConfig } from "./installers.types"; export function createInstaller( @@ -28,5 +29,3 @@ export function createInstaller( }, }; } - -export const installer = createInstaller; diff --git a/src/fastify/installers.types.ts b/src/fastify/installers.types.ts index 211d061..0df0de2 100644 --- a/src/fastify/installers.types.ts +++ b/src/fastify/installers.types.ts @@ -1,7 +1,7 @@ import type { FastifyInstance } from "fastify"; import type { ProvidersMap, ExposeDeps } from "../../src/providers"; import type { Container } from "../../src/container/container"; -import { ModuleContext } from "../modules"; +import type { ModuleContext } from "../modules/module.types"; /** * Installer builder callback. diff --git a/src/hooks/hooks.ts b/src/hooks/hooks.ts index 2f303c1..49d3d3f 100644 --- a/src/hooks/hooks.ts +++ b/src/hooks/hooks.ts @@ -1,5 +1,5 @@ import { FastifyInstance } from "fastify"; -import { ProvidersMap } from "../providers"; +import type { ProvidersMap } from "../providers/providers.types"; import { AppHooksConfig, AppHooksOptions, @@ -9,8 +9,10 @@ import { import { Container } from "../container/container"; import { HttpHooksBuilder } from "./http-hooks-builder"; import { AppHooksBuilder } from "./application-hooks-builder"; -import { ModuleContext, resolveProviderMap } from "../modules"; -import { AdapterCache, AdapterMap, resolveAdapterMap } from "../fastify"; +import { resolveProviderMap } from "../modules/module"; +import type { ModuleContext } from "../modules/module.types"; +import { resolveAdapterMap } from "../fastify/adapters"; +import type { AdapterCache, AdapterMap } from "../fastify/adapters.types"; export function createHooks< Providers extends ProvidersMap, @@ -71,5 +73,3 @@ export function createHooks< }, }; } - -export const hooks = createHooks; diff --git a/src/hooks/hooks.types.ts b/src/hooks/hooks.types.ts index 542e670..2ba2021 100644 --- a/src/hooks/hooks.types.ts +++ b/src/hooks/hooks.types.ts @@ -7,13 +7,16 @@ import { RequestPayload, RouteOptions, } from "fastify"; -import { ApplicationHook, LifecycleHook } from "fastify/types/hooks"; import { HttpHooksBuilder } from "./http-hooks-builder"; import { ExposeDeps, ProvidersMap } from "../providers"; import { Container } from "../container/container"; import { AppHooksBuilder } from "./application-hooks-builder"; -import { AdapterCache, AdapterMap, AdapterValues } from "../fastify"; -import { ModuleContext } from "../modules"; +import type { + AdapterCache, + AdapterMap, + AdapterValues, +} from "../fastify/adapters.types"; +import type { ModuleContext } from "../modules/module.types"; // --- HTTP hooks --- export type OnRequestHandler = ( @@ -60,8 +63,6 @@ export type OnRequestAbortHandler = ( request: FastifyRequest, ) => Promise; -export type HttpHookName = LifecycleHook; - export type HttpHookMap = { onRequest: OnRequestHandler[]; preParsing: OnPreParsingHandler[]; @@ -75,6 +76,8 @@ export type HttpHookMap = { onRequestAbort: OnRequestAbortHandler[]; }; +export type HttpHookName = keyof HttpHookMap; + export type HttpHookHandlers = HttpHookMap[K]; export type HttpHookHandler = HttpHookHandlers[number]; @@ -133,8 +136,6 @@ export type OnRegisterHandler = ( opts: RegisterOptions & FastifyPluginOptions, ) => void; -export type AppHookName = ApplicationHook; - export type AppHookMap = { onReady: OnReadyHandler[]; onClose: OnCloseHandler[]; @@ -144,6 +145,8 @@ export type AppHookMap = { preClose: OnPreCloseHandler[]; }; +export type AppHookName = keyof AppHookMap; + type ApplicationHooksBuilderCallback< Providers extends ProvidersMap, Adaps extends AdapterMap, diff --git a/src/hooks/index.ts b/src/hooks/index.ts index f1e5f17..21a8011 100644 --- a/src/hooks/index.ts +++ b/src/hooks/index.ts @@ -1,4 +1,29 @@ -export * from "./hooks.types"; -export * from "./hooks"; -export * from "./http-hooks-builder"; -export * from "./application-hooks-builder"; +export { createHooks, createHooks as hooks } from "./hooks"; +export type { + AppHookMap, + AppHookName, + AppHooksConfig, + AppHooksOptions, + HttpHookHandler, + HttpHookHandlers, + HttpHookMap, + HttpHookName, + HttpHooksConfig, + HttpHooksOptions, + OnCloseHandler, + OnErrorHookHandler, + OnListenHandler, + OnPreCloseHandler, + OnPreHandlerHandler, + OnPreParsingHandler, + OnPreSerializationHandler, + OnPreValidationHandler, + OnReadyHandler, + OnRegisterHandler, + OnRequestAbortHandler, + OnRequestHandler, + OnResponseHandler, + OnRouteHandler, + OnSendHookHandler, + OnTimeoutHandler, +} from "./hooks.types"; diff --git a/src/index.ts b/src/index.ts index 3af1a4c..35c66cf 100644 --- a/src/index.ts +++ b/src/index.ts @@ -9,10 +9,12 @@ export * from "./hooks"; export * from "./controllers"; export * from "./fastify"; -import { getProviderId, resolveDeps, type ProviderAny } from "./providers"; +import { getProviderId, resolveDeps } from "./providers/providers"; +import type { ProviderAny } from "./providers/providers.types"; import { Container } from "./container/container"; import { describeTree } from "./printer/describe-tree"; -import { getModuleId, ModuleAny, registerModule } from "./modules"; +import { getModuleId, registerModule } from "./modules/module"; +import type { ModuleAny } from "./modules/module.types"; export interface CreateAppOptions { root: ModuleAny; diff --git a/src/modules/index.ts b/src/modules/index.ts index 6764d6c..d7e534a 100644 --- a/src/modules/index.ts +++ b/src/modules/index.ts @@ -1,3 +1,7 @@ -export * from "./module"; -export * from "./module.types"; -export * from "../controllers/controllers.types"; +export { createModule, createModule as mod } from "./module"; +export type { + ModuleAny, + ModuleDef, + ModuleOptions, + SubModulesMap, +} from "./module.types"; diff --git a/src/modules/module.ts b/src/modules/module.ts index f796516..cf55928 100644 --- a/src/modules/module.ts +++ b/src/modules/module.ts @@ -8,8 +8,8 @@ import { ModuleOptions, SubModulesMap, } from "./module.types"; -import { ProvidersMap } from "../providers"; -import { AdapterCache } from "../fastify"; +import type { AdapterCache } from "../fastify/adapters.types"; +import type { ProvidersMap } from "../providers/providers.types"; const kModuleId = Symbol("fastify-dependency-injection:moduleId"); let __seq = 0; @@ -97,5 +97,3 @@ export async function resolveProviderMap( return out; } - -export const mod = createModule; diff --git a/src/printer/describe-tree.ts b/src/printer/describe-tree.ts index 482d2cb..4a58f1c 100644 --- a/src/printer/describe-tree.ts +++ b/src/printer/describe-tree.ts @@ -1,6 +1,8 @@ -import { AdapterAny } from "../fastify"; -import { getModuleId, type ModuleAny } from "../modules"; -import { getProviderId, type ProviderAny } from "../providers"; +import type { AdapterAny } from "../fastify/adapters.types"; +import { getModuleId } from "../modules/module"; +import type { ModuleAny } from "../modules/module.types"; +import { getProviderId } from "../providers/providers"; +import type { ProviderAny } from "../providers/providers.types"; const useColor = process.stdout.isTTY; const wrap = (code: number, s: string, enabled: boolean) => { diff --git a/src/providers/index.ts b/src/providers/index.ts index 7ac3ef0..13b538d 100644 --- a/src/providers/index.ts +++ b/src/providers/index.ts @@ -1,2 +1,16 @@ -export * from "./providers"; -export * from "./providers.types"; +export { + contract, + createProvider, + createProvider as provider, +} from "./providers"; +export type { + BaseProviderDepsMap, + DepValues, + ExposeDeps, + InferProviderContract, + ProviderAny, + ProviderContract, + ProviderDef, + ProviderOptions, + ProvidersMap, +} from "./providers.types"; diff --git a/src/providers/providers.ts b/src/providers/providers.ts index 85ebb17..40b47cf 100644 --- a/src/providers/providers.ts +++ b/src/providers/providers.ts @@ -1,5 +1,5 @@ import { Container } from "../container/container"; -import { ModuleContext } from "../modules"; +import type { ModuleContext } from "../modules/module.types"; import { deepClone } from "../utils/deep-clone"; import { BaseProviderDepsMap, @@ -68,8 +68,6 @@ export function createProvider< return self; } -export const provider = createProvider; - export async function resolveDeps( container: Container, prov: ProviderAny, diff --git a/src/public-api.test-d.ts b/src/public-api.test-d.ts new file mode 100644 index 0000000..20c57e2 --- /dev/null +++ b/src/public-api.test-d.ts @@ -0,0 +1,33 @@ +import { expectError, expectType } from "tsd"; +import * as api from "."; +import { + adapter, + controller, + createAdapter, + createController, + createHooks, + createInstaller, + createModule, + createProvider, + hooks, + installer, + mod, + provider, +} from "."; + +expectType(provider); +expectType(mod); +expectType(hooks); +expectType(controller); +expectType(installer); +expectType(adapter); + +expectError(api.getProviderId); +expectError(api.resolveDeps); +expectError(api.getModuleId); +expectError(api.registerModule); +expectError(api.resolveProviderMap); +expectError(api.resolveAdapterMap); +expectError(api.HttpHooksBuilder); +expectError(api.AppHooksBuilder); +expectError(api.RoutesBuilder); diff --git a/src/public-api.test.ts b/src/public-api.test.ts new file mode 100644 index 0000000..7f6fa54 --- /dev/null +++ b/src/public-api.test.ts @@ -0,0 +1,36 @@ +import assert from "node:assert/strict"; +import { describe, test } from "node:test"; +import * as api from "./index"; + +describe("public API", () => { + test("only exposes supported runtime utilities", () => { + const exports = Object.keys(api).sort(); + + // Type-only exports are erased, so this list is the complete JavaScript API. + assert.deepStrictEqual(exports, [ + "adapter", + "contract", + "controller", + "createAdapter", + "createApp", + "createController", + "createHooks", + "createInstaller", + "createModule", + "createProvider", + "hooks", + "installer", + "mod", + "provider", + ]); + }); + + test("exports factory shortcuts as direct aliases", () => { + assert.strictEqual(api.provider, api.createProvider); + assert.strictEqual(api.mod, api.createModule); + assert.strictEqual(api.hooks, api.createHooks); + assert.strictEqual(api.controller, api.createController); + assert.strictEqual(api.installer, api.createInstaller); + assert.strictEqual(api.adapter, api.createAdapter); + }); +});