/** * Core type definitions for Le Truc. * This file contains types that are shared across multiple modules. */ import type { MaybeCleanup } from '@zeix/cause-effect' /* === Constants === */ /** Symbol brand applied to all Parser functions. */ const PARSER_BRAND: unique symbol = Symbol('parser') /** Symbol brand applied to all MethodProducer functions. */ const METHOD_BRAND: unique symbol = Symbol('method') /* === Types === */ /** A branded parser function (transforms HTML attribute strings to typed values). */ type Parser = (value: string | null | undefined) => T /** A branded method-producer function (side-effect initializer, returns void). */ type MethodProducer = ((...args: any[]) => void) & { readonly [METHOD_BRAND]: true } /** * Property names that must not be used as reactive component properties * because they are fundamental JavaScript / `Object` builtins. */ type ReservedWords = | 'constructor' | 'prototype' | '__proto__' | 'toString' | 'valueOf' | 'hasOwnProperty' | 'isPrototypeOf' | 'propertyIsEnumerable' | 'toLocaleString' /** A valid reactive property name — any string that is not an `HTMLElement` or `ReservedWords` key. */ type ComponentProp = Exclude /** A record of reactive property names to their value types, used to type a component's props. */ type ComponentProps = Record> type Falsy = false | null | undefined | '' | 0 | 0n /** * A deferred effect: a thunk that, when called inside a reactive scope, creates * a reactive effect and returns an optional cleanup function. * * Effect descriptors are returned by `watch()`, `on()`, `each()`, `pass()`, and * `provideContexts()`. They are activated after dependency resolution, not * immediately when the factory function runs. */ type EffectDescriptor = () => MaybeCleanup /** * The return value of the factory function. * * An array of effect descriptors (and optional falsy guards for conditional * effects). Nested arrays are automatically flattened. Falsy values (`false`, * `undefined`, `null`, `""`, `0`) are filtered out before activation, enabling the * `element && [watch(...)]` conditional pattern. */ type FactoryResult = Array /* === Exported Functions === */ /** * Check if a value is a parser * * Checks for the `PARSER_BRAND` symbol. Unbranded functions are NOT treated as * parsers — always use `asParser()` to brand custom parsers. * * @since 0.14.0 * @param {unknown} value - Value to check if it is a parser * @returns {boolean} True if the value is a parser, false otherwise */ const isParser = (value: unknown): value is Parser => typeof value === 'function' && PARSER_BRAND in value /** * Check if a value is a MethodProducer (branded side-effect initializer) * * @since 0.16.2 * @param {unknown} value - Value to check * @returns {boolean} True if the value is a MethodProducer */ const isMethodProducer = (value: unknown): value is MethodProducer => typeof value === 'function' && METHOD_BRAND in value /** * Brand a custom parser function with the `PARSER_BRAND` symbol. * * Use this to wrap any custom parser so `isParser()` can identify it reliably. * * @since 0.16.2 * @param {Parser} fn - Custom parser function to brand * @returns {Parser} The same function, branded */ const asParser = (fn: Parser): Parser => Object.assign(fn, { [PARSER_BRAND]: true as const }) /** * Brand a custom method-producer function with the `METHOD_BRAND` symbol. * * Use this to wrap any side-effect initializer so `isMethodProducer()` can * identify it explicitly rather than relying on the absence of a return value. * * @since 0.16.2 * @param {T} fn - Side-effect initializer to brand * @returns {T & { readonly [METHOD_BRAND]: true }} The same function, branded as a `MethodProducer` */ const defineMethod = void>( fn: T, ): T & { readonly [METHOD_BRAND]: true } => Object.assign(fn, { [METHOD_BRAND]: true as const }) export { asParser, type ComponentProp, type ComponentProps, defineMethod, type EffectDescriptor, type FactoryResult, type Falsy, isMethodProducer, isParser, type MethodProducer, type Parser, type ReservedWords, }