import {LitElementStateService} from './litElementState.service.js'; export interface StateConfig { cache?: { name?: string; exceptions?: RegExp[]; handlers: CacheHandler[]; }; defaultSubscribeOptions?: SubscribeStateFromElementOptions; global: boolean; } export interface CacheHandler { name: string; set(change: StateChange, stateServiceInstance: LitElementStateService): Promise; load(stateServiceInstance: LitElementStateService): Promise>; } export type StateSubscriptionFunction = ( value: Change ) => void; export interface Change

{ readonly previous: P | null readonly current: P | null } export type StateReducerMode = 'merge' | 'replace'; export type PredicateFunction = (array: ArrayType, index?: number) => boolean; export type IndexOrPredicateFunction = number | PredicateFunction; export type ArrayElementSelector = { array: ArrayName, get: IndexOrPredicateFunction }; export interface GetStateOptions { getDeepCopy?: boolean; } export interface SubscribeStateOptions extends GetStateOptions { getInitialValue?: boolean; // Set true to trigger changes when a sub-property of a subscribed property changes pushNestedChanges?: boolean; } export interface SubscribeStateFromElementOptions extends SubscribeStateOptions { autoUnsubscribe?: boolean; } export interface SetStateOptions { // Provide the name of a cache handler to use it for persistence with this set state call cacheHandlerName?: string; } export type StateChange = State extends Array ? { _arrayOperation: { op: 'update', at: IndexOrPredicateFunction, val: StateChange | ((element: State[number]) => StateChange) } | { op: 'push', at?: number, val: State[number] } | { op: 'pull', at?: IndexOrPredicateFunction } } | State : { [P in keyof State]?: State[P] | StateChange } & { _reducerMode?: StateReducerMode }; // ─── Typed state paths ────────────────────────────────────────────────────── // // One generic signature per path method (subscribe / get / subscribeState / // connectState): // // method>(path: CheckedStatePath, ...) // // The given path is validated segment by segment — linear in the path length // (possible paths of State are NEVER enumerated). String literals are kept // without `as const`, optional properties work at any depth, there is no depth // cap, and selector predicates (`get: field => ...`) are auto-typed without // parameter annotations. // // LOAD-BEARING INVARIANTS — established empirically on TS 5.9/6.0 and guarded // by the suite in type-tests/. Referenced below as [1]..[6]: // [1] An unannotated predicate makes the path literal context-sensitive, so it // is contextually typed BEFORE P can be inferred. That context can only // come from P's CONSTRAINT, which therefore must be a PLAIN union (never // intersected into the parameter type — discrimination fails through // intersections) and a DISTRIBUTIVE conditional on State (otherwise the // receiver's State is not substituted on generic classes). // [2] Selector shapes in that union must be INLINE. Alias chains // (ArrayElementSelector -> IndexOrPredicateFunction -> PredicateFunction) // reach the literal un-instantiated and kill discrimination. // [3] The discriminant may match only ONE member carrying a call signature, // or the checker drops the contextual signature entirely. Hence: // AllowedSegments is KEYS-ONLY (leaked selector members would collide), // same-named arrays are MERGED into one member (element union), and // pattern keys — index signatures / template-literal keys, which match // every literal name — are SIGNATURE-FREE (`get: unknown`). // [4] `| Function` in a merged member admits predicates annotated with one of // the colliding element types (parameter contravariance would reject them // against the union parameter) while adding no call signature; the // position-exact final check still rejects mismatches. // [5] Pattern-key entries carry a DIFFERENT tag than literal entries: a plain // `string` name in the same union would absorb every literal name // (`'data' | string` reduces to `string`). // [6] `never extends readonly any[]` and `any extends readonly any[]` are both // true — never-/any-typed properties must be guarded out of array checks. /** True only for `any`. */ type IsAny = 0 extends (1 & T) ? true : false; /** What the runtime's `Array.isArray` accepts: arrays, readonly arrays, tuples. */ type AnyArray = readonly any[]; /** Element type of an array-typed value. */ type ElementOf = NonNullable extends readonly (infer E)[] ? E : never; /** Keys of T holding arrays (guards per [6]). */ type ArrayKeys = { [K in keyof NonNullable]-?: IsAny[K]> extends true ? never : NonNullable[K]> extends AnyArray ? K : never }[keyof NonNullable]; /** Keys of T not holding arrays. */ type NonArrayKeys = Exclude, ArrayKeys>; /** One type-level step of the runtime path walk. NonNullable first, so optional * intermediates never collapse `keyof` to `never`; invalid segments descend to * `any` so only the segment that is already reported errors, not its suffix. */ type Descend = IsAny extends true ? any : Seg extends { array: infer A } ? (A extends keyof NonNullable ? ElementOf[A]> : any) : (Seg extends keyof NonNullable ? NonNullable[Seg] : any); /** Value at the end of path P. A plain array key in last position yields the * whole array (mirrors getStateData); non-tuple P (dynamic path) yields any. */ export type StatePathValue = number extends P['length'] ? any : P extends readonly [infer Head, ...infer Rest] ? (Rest extends readonly [unknown, ...unknown[]] ? StatePathValue, Rest> : Descend) : (IsAny extends true ? any : undefined); /** Position-exact selector for array key K of T — INLINE shape per [2]. */ type ArraySelectorFor = K extends keyof NonNullable ? { array: K, get: number | ((element: ElementOf[K]>, index?: number) => boolean) } : never; /** All position-exact selectors of one node T. */ type ArraySelectors = { [K in ArrayKeys]: ArraySelectorFor }[ArrayKeys]; /** Plain keys allowed at a node: array keys only in last position (whole-array * subscription); mid-path an array requires a selector. KEYS-ONLY per [3]. */ type AllowedSegments = IsLast extends true ? keyof NonNullable : NonArrayKeys; /** Expected type of one segment: a well-formed selector resolves to its * position-exact shape, everything else to the allowed alternatives — which * drive both the error message and the IDE completions. */ type SegmentOut = IsAny extends true ? Seg : [Seg] extends [{ array: infer A }] ? ([A] extends [ArrayKeys] ? ArraySelectorFor : ArraySelectors) : AllowedSegments; /** Expected tuple for path P (tail-recursive — exempt from the instantiation * depth limit). Non-tuple P degrades to the loose constraint shape; this covers * dynamic path variables AND the transient constraint-fixed moment while a * context-sensitive literal is contextually typed [1] — afterwards P is * inferred from the typed literal, so the final check is position-exact even * for unannotated predicates. The empty path stays an arity error. */ type ValidatePath = P extends readonly [infer Head, ...infer Rest] ? (Rest extends readonly [unknown, ...unknown[]] ? ValidatePath, Rest, readonly [...Acc, SegmentOut]> : readonly [...Acc, SegmentOut]) : P extends readonly [] ? readonly [...Acc, AllowedSegments] : StatePath; /** P itself when it matches the expected tuple (the naked P here carries the * literal inference), otherwise the expected tuple (per-segment errors). */ type ValidateShape = P extends Expected ? P : Expected; /** Non-unit string keys: `string` itself and template-literal patterns (a * Record over such a key has no required properties). */ type IsPatternKey = {} extends Record ? true : false; /** Walk collecting an entry for every array-typed property reachable in T * (depth-capped, linear in the state size, cached per T — NOT a path * enumeration). Literal keys are tagged `array`, pattern keys `patternArray` * per [5]; guards per [6]; numeric/symbol keys are not addressable by the * runtime's string segments. */ type DeepArrayEntries = Depth['length'] extends 10 ? never : IsAny extends true ? never : NonNullable extends infer U ? U extends readonly (infer E)[] ? DeepArrayEntries : U extends object ? { [K in keyof U]-?: | (IsAny extends true ? never : [NonNullable] extends [never] ? never : NonNullable extends AnyArray ? (K extends string ? (IsPatternKey extends true ? { patternArray: K, element: ElementOf } : { array: K, element: ElementOf }) : never) : never) | DeepArrayEntries }[keyof U] : never : never; /** All literal entry names (pattern entries stay out per [5]). */ type EntryNames = Entries extends { array: infer N extends string } ? N : never; /** Union of the element types of every entry named N (same-name merge, [3]). */ type ElementForName = Entries extends { array: N, element: infer E } ? E : never; /** The name-discriminated selector union that auto-types unannotated predicate * parameters [1]: one MERGED member per literal array name (element union, * [3]; `| Function` per [4]) plus SIGNATURE-FREE members for pattern names * ([3] — their predicates must be annotated). */ type DeepArraySelectors = DeepArrayEntries extends infer Entries ? [Entries] extends [never] ? never : | (EntryNames extends infer Names ? (Names extends string ? { array: Names, get: number | ((element: ElementForName, index?: number) => boolean) | Function } : never) : never) | (Entries extends { patternArray: infer PN extends string } ? { array: PN, get: unknown } : never) : never; /** The public path type: constraint of the path type parameter (any sequence * of keys and well-formed selectors for arrays existing anywhere in State) * and the type for DYNAMIC (non-literal) path variables. Doubles as the * contextual type that auto-types unannotated predicates — plain union + * distributive conditional are both required, see [1]. * * NOTE: do NOT use StatePath/StatePath for state-agnostic * storage — DeepArraySelectors guards `any` out (per [6]) and finds nothing * in `unknown`, so both collapse to `readonly string[]` and reject selector * segments. State-agnostic code (e.g. LitElementStateSubscription.path) * inlines the loose shape `readonly (string | ArrayElementSelector)[]`. */ export type StatePath = State extends unknown ? readonly (string | DeepArraySelectors)[] : never; /** Parameter type of the public path methods: position-exact validation and * value/predicate typing for literal paths (including unannotated predicates, * see ValidatePath); only dynamic (non-literal) paths stay loosely typed. */ export type CheckedStatePath = ValidateShape>; // Root entry exposes the service, the stateful element base class, the // subscription and the shared types. Cache handlers are intentionally NOT // re-exported here so IDE auto-import and bundlers resolve them via their // dedicated subpaths, e.g. `@stefanholzapfel/lit-state/localStorageCacheHandler.js`. export * from './litElementStateful.js'; export * from './litElementState.service.js'; export * from './litElementStateSubscription.js'