import type { z, ZodObject, ZodOptional, ZodRecord } from 'zod/v4' import type { FactoryContext } from '../internal/context/factory-context.mjs' export type ClassType = new (...args: any[]) => any export type ClassTypeWithoutArguments = new () => any export type ClassTypeWithArgument = new (arg: Arg) => any export type ClassTypeWithOptionalArgument = new (arg?: Arg) => any export type ClassTypeWithInstance = new (...args: any[]) => T export type ClassTypeWithInstanceAndArgument = new (arg: Arg) => T export type ClassTypeWithInstanceAndOptionalArgument = new ( arg?: Arg, ) => T export type BaseInjectionTokenSchemaType = ZodObject | ZodRecord export type OptionalInjectionTokenSchemaType = | ZodOptional | ZodOptional export type InjectionTokenSchemaType = | BaseInjectionTokenSchemaType | OptionalInjectionTokenSchemaType /** * Simple hash function for deterministic ID generation */ function simpleHash(str: string): string { let hash = 0 for (let i = 0; i < str.length; i++) { const char = str.charCodeAt(i) hash = (hash << 5) - hash + char hash = hash & hash // Convert to 32-bit integer } return Math.abs(hash).toString(36) } /** * Generate deterministic ID from token name */ function generateTokenId(name: string | symbol | ClassType, customId?: string): string { if (customId) { return customId } let base: string if (typeof name === 'function') { base = `${name.name}_${name.toString()}` } else if (typeof name === 'symbol') { base = `symbol_${name.toString()}` } else { base = `token_${name}` } return `${base.split('_')[0]}_${simpleHash(base)}` } export class InjectionToken< // oxlint-disable-next-line no-unused-vars T, S extends InjectionTokenSchemaType | unknown = unknown, // oxlint-disable-next-line no-unused-vars Required extends boolean = S extends ZodOptional ? false : S extends ZodOptional ? false : S extends ZodObject ? true : S extends ZodRecord ? true : false, > { public readonly id: string private formattedName: string | null = null constructor( public readonly name: string | symbol | ClassType, public readonly schema: ZodObject | undefined, customId?: string, ) { this.id = generateTokenId(name, customId) } static create( name: T, ): InjectionToken, undefined> static create( name: T, schema: Schema, ): Schema['_def']['type'] extends 'ZodOptional' ? InjectionToken, Schema, false> : InjectionToken, Schema, true> static create(name: string | symbol): InjectionToken static create( name: string | any, schema: Schema, ): InjectionToken static create(name: string | symbol, schema?: unknown, customId?: string) { // @ts-expect-error return new InjectionToken(name, schema, customId) } static bound( token: InjectionToken, value: z.input, ): BoundInjectionToken { return new BoundInjectionToken(token, value) } static factory( token: InjectionToken, factory: (ctx: FactoryContext) => Promise>, ): FactoryInjectionToken { return new FactoryInjectionToken(token, factory) } static refineType( token: BoundInjectionToken, ): BoundInjectionToken { return token as BoundInjectionToken } toString() { if (this.formattedName) { return this.formattedName } const { name } = this if (typeof name === 'function') { const className = name.name this.formattedName = `${className}(${this.id})` } else if (typeof name === 'symbol') { this.formattedName = `${name.toString()}(${this.id})` } else { this.formattedName = `${name}(${this.id})` } return this.formattedName } } export class BoundInjectionToken { public id: string public name: string | symbol | ClassType public schema: InjectionTokenSchemaType constructor( public readonly token: InjectionToken, public readonly value: z.input, ) { this.name = token.name this.id = token.id this.schema = token.schema as InjectionTokenSchemaType } toString() { return this.token.toString() } } export class FactoryInjectionToken { public value?: z.input public resolved = false public id: string public name: string | symbol | ClassType public schema: InjectionTokenSchemaType constructor( public readonly token: InjectionToken, public readonly factory: (ctx: FactoryContext) => Promise>, ) { this.name = token.name this.id = token.id this.schema = token.schema as InjectionTokenSchemaType } async resolve(ctx: FactoryContext): Promise> { if (!this.value) { this.value = await this.factory(ctx) this.resolved = true } return this.value } toString() { return this.token.toString() } } export type AnyInjectableType = | ClassType | InjectionToken | BoundInjectionToken | FactoryInjectionToken export type InjectionTokenType = | InjectionToken | BoundInjectionToken | FactoryInjectionToken