@sladg/apex-state 3.5.0 → 3.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -121,6 +121,70 @@ type RecordLevel2<V, ParentKey extends string, Depth extends number> = IsAny$1<V
121
121
  }[keyof V & (string | number)];
122
122
  type Prev2<N extends number> = N extends 20 ? 18 : N extends 19 ? 17 : N extends 18 ? 16 : N extends 17 ? 15 : N extends 16 ? 14 : N extends 15 ? 13 : N extends 14 ? 12 : N extends 13 ? 11 : N extends 12 ? 10 : N extends 11 ? 9 : N extends 10 ? 8 : N extends 9 ? 7 : N extends 8 ? 6 : N extends 7 ? 5 : N extends 6 ? 4 : N extends 5 ? 3 : N extends 4 ? 2 : N extends 3 ? 1 : N extends 2 ? 0 : N extends 1 ? 0 : never;
123
123
 
124
+ /**
125
+ * DeepKeyFiltered - Filters paths by their resolved value type
126
+ *
127
+ * Returns only paths from DeepKey<T> that resolve to a specific type U.
128
+ * Useful for type-safe filtering of state paths by their value types.
129
+ *
130
+ * @example
131
+ * ```typescript
132
+ * type Data = {
133
+ * isActive: boolean
134
+ * name: string
135
+ * count: number
136
+ * user: { isAdmin: boolean }
137
+ * }
138
+ *
139
+ * // Only boolean paths
140
+ * type BoolPaths = DeepKeyFiltered<Data, boolean>
141
+ * // Result: "isActive" | "user.isAdmin"
142
+ *
143
+ * // Only string paths
144
+ * type StrPaths = DeepKeyFiltered<Data, string>
145
+ * // Result: "name"
146
+ * ```
147
+ */
148
+
149
+ /**
150
+ * Filters DeepKey<T> to only include paths that resolve to type U.
151
+ *
152
+ * Uses mapped type with conditional filtering - maps over all possible paths,
153
+ * keeps those matching the target type, and extracts the union.
154
+ *
155
+ * @example Custom depth — propagates to DeepKey
156
+ * ```typescript
157
+ * // Only number paths, limited to 10 levels deep
158
+ * type ShallowNumbers = DeepKeyFiltered<State, number, 10>
159
+ * ```
160
+ */
161
+ type DeepKeyFiltered<T, U, Depth extends number = DefaultDepth> = {
162
+ [K in ResolvableDeepKey<T, Depth>]: NonNullable<DeepValue<T, K>> extends U ? K : never;
163
+ }[ResolvableDeepKey<T, Depth>];
164
+
165
+ /**
166
+ * DeepValue utility type
167
+ *
168
+ * Extracts the value type for a given dot-notation path string.
169
+ * Handles nested objects, arrays, and optional properties.
170
+ *
171
+ * @example
172
+ * ```typescript
173
+ * type User = {
174
+ * address: {
175
+ * street: string
176
+ * city: string
177
+ * }
178
+ * }
179
+ *
180
+ * // DeepValue<User, "address.street"> = string
181
+ * // DeepValue<User, "address"> = { street: string, city: string }
182
+ * ```
183
+ */
184
+
185
+ type IsAny<T> = 0 extends 1 & T ? true : false;
186
+ type DeepValue<T, Path extends string> = IsAny<T> extends true ? never : T extends readonly any[] ? T[number] : Path extends `${infer First}.${infer Rest}` ? First extends keyof T ? DeepValue<T[First], Rest> : string extends keyof T ? First extends HASH_KEY ? DeepValue<T[string], Rest> : unknown : unknown : Path extends HASH_KEY ? string extends keyof T ? T[string] : unknown : Path extends keyof T ? T[Path] : unknown;
187
+
124
188
  /**
125
189
  * Boolean logic DSL type definitions
126
190
  *
@@ -129,9 +193,23 @@ type Prev2<N extends number> = N extends 20 ? 18 : N extends 19 ? 17 : N extends
129
193
  */
130
194
 
131
195
  /**
132
- * Primitive types that can be compared in BoolLogic expressions
196
+ * Path-value pair distributed over all valid paths.
197
+ * Each path is paired with its resolved value type, ensuring type safety.
198
+ * The distribution happens inside the tuple, not at the union level —
199
+ * this keeps BoolLogic's top-level union flat so `in` narrowing works.
200
+ */
201
+ type PathValuePair<STATE, Depth extends number> = DeepKey<STATE, Depth> extends infer P ? P extends string ? [P, DeepValue<STATE, P>] : never : never;
202
+ /**
203
+ * Path-value array pair for IN operator.
204
+ * Same distribution as PathValuePair but with array values.
205
+ */
206
+ type PathValueArrayPair<STATE, Depth extends number> = DeepKey<STATE, Depth> extends infer P ? P extends string ? [P, DeepValue<STATE, P>[]] : never : never;
207
+ /**
208
+ * Paths that resolve to number, plus `.length` on array-valued paths.
209
+ * Allows GT/LT/GTE/LTE to compare against array lengths without
210
+ * polluting DeepKey with virtual paths.
133
211
  */
134
- type ComparableValue = string | number | boolean | null | undefined;
212
+ type NumericPaths<STATE, Depth extends number> = DeepKeyFiltered<STATE, number, Depth> | `${DeepKeyFiltered<STATE, readonly unknown[], Depth>}.length`;
135
213
  /**
136
214
  * Boolean logic DSL for conditional expressions
137
215
  *
@@ -139,33 +217,40 @@ type ComparableValue = string | number | boolean | null | undefined;
139
217
  * Used by concerns (disabledWhen, visibleWhen) and side effects.
140
218
  *
141
219
  * Operators:
142
- * - IS_EQUAL: Compare path value to expected value
220
+ * - IS_EQUAL: Compare path value to expected value (value must match path type)
143
221
  * - EXISTS: Check if path value is not null/undefined
144
222
  * - IS_EMPTY: Check if path value is empty (string/array/object)
145
223
  * - AND/OR/NOT: Boolean combinators
146
- * - GT/LT/GTE/LTE: Numeric comparisons
147
- * - IN: Check if path value is in allowed list
224
+ * - GT/LT/GTE/LTE: Numeric comparisons (only on number paths or array.length)
225
+ * - IN: Check if path value is in allowed list (values must match path type)
226
+ * - Shorthand: [path, value] tuple as shorthand for IS_EQUAL
148
227
  *
149
228
  * @example
150
229
  * ```typescript
151
230
  * // Simple equality check
152
231
  * const isAdmin: BoolLogic<State> = { IS_EQUAL: ['user.role', 'admin'] }
153
232
  *
154
- * // Combined conditions
233
+ * // Shorthand equality (equivalent to IS_EQUAL)
234
+ * const isAdmin2: BoolLogic<State> = ['user.role', 'admin']
235
+ *
236
+ * // Combined conditions with shorthand
155
237
  * const canEdit: BoolLogic<State> = {
156
238
  * AND: [
157
- * { IS_EQUAL: ['user.role', 'editor'] },
239
+ * ['user.role', 'editor'],
158
240
  * { EXISTS: 'document.id' },
159
- * { NOT: { IS_EQUAL: ['document.status', 'locked'] } }
241
+ * { NOT: ['document.status', 'locked'] }
160
242
  * ]
161
243
  * }
162
244
  *
163
245
  * // Numeric comparison
164
246
  * const isExpensive: BoolLogic<State> = { GT: ['product.price', 100] }
247
+ *
248
+ * // Array length comparison
249
+ * const hasManyItems: BoolLogic<State> = { GT: ['cart.items.length', 5] }
165
250
  * ```
166
251
  */
167
252
  type BoolLogic<STATE, Depth extends number = DefaultDepth> = {
168
- IS_EQUAL: [DeepKey<STATE, Depth>, ComparableValue];
253
+ IS_EQUAL: PathValuePair<STATE, Depth>;
169
254
  } | {
170
255
  EXISTS: DeepKey<STATE, Depth>;
171
256
  } | {
@@ -177,39 +262,16 @@ type BoolLogic<STATE, Depth extends number = DefaultDepth> = {
177
262
  } | {
178
263
  NOT: BoolLogic<STATE, Depth>;
179
264
  } | {
180
- GT: [DeepKey<STATE, Depth>, number];
265
+ GT: [NumericPaths<STATE, Depth>, number];
181
266
  } | {
182
- LT: [DeepKey<STATE, Depth>, number];
267
+ LT: [NumericPaths<STATE, Depth>, number];
183
268
  } | {
184
- GTE: [DeepKey<STATE, Depth>, number];
269
+ GTE: [NumericPaths<STATE, Depth>, number];
185
270
  } | {
186
- LTE: [DeepKey<STATE, Depth>, number];
271
+ LTE: [NumericPaths<STATE, Depth>, number];
187
272
  } | {
188
- IN: [DeepKey<STATE, Depth>, ComparableValue[]];
189
- };
190
-
191
- /**
192
- * DeepValue utility type
193
- *
194
- * Extracts the value type for a given dot-notation path string.
195
- * Handles nested objects, arrays, and optional properties.
196
- *
197
- * @example
198
- * ```typescript
199
- * type User = {
200
- * address: {
201
- * street: string
202
- * city: string
203
- * }
204
- * }
205
- *
206
- * // DeepValue<User, "address.street"> = string
207
- * // DeepValue<User, "address"> = { street: string, city: string }
208
- * ```
209
- */
210
-
211
- type IsAny<T> = 0 extends 1 & T ? true : false;
212
- type DeepValue<T, Path extends string> = IsAny<T> extends true ? never : T extends readonly any[] ? T[number] : Path extends `${infer First}.${infer Rest}` ? First extends keyof T ? DeepValue<T[First], Rest> : string extends keyof T ? First extends HASH_KEY ? DeepValue<T[string], Rest> : unknown : unknown : Path extends HASH_KEY ? string extends keyof T ? T[string] : unknown : Path extends keyof T ? T[Path] : unknown;
273
+ IN: PathValueArrayPair<STATE, Depth>;
274
+ } | PathValuePair<STATE, Depth>;
213
275
 
214
276
  /**
215
277
  * GenericMeta interface
@@ -422,47 +484,6 @@ type ConcernRegistrationMap<DATA extends object, CONCERNS extends readonly any[]
422
484
  }>;
423
485
  }>;
424
486
 
425
- /**
426
- * DeepKeyFiltered - Filters paths by their resolved value type
427
- *
428
- * Returns only paths from DeepKey<T> that resolve to a specific type U.
429
- * Useful for type-safe filtering of state paths by their value types.
430
- *
431
- * @example
432
- * ```typescript
433
- * type Data = {
434
- * isActive: boolean
435
- * name: string
436
- * count: number
437
- * user: { isAdmin: boolean }
438
- * }
439
- *
440
- * // Only boolean paths
441
- * type BoolPaths = DeepKeyFiltered<Data, boolean>
442
- * // Result: "isActive" | "user.isAdmin"
443
- *
444
- * // Only string paths
445
- * type StrPaths = DeepKeyFiltered<Data, string>
446
- * // Result: "name"
447
- * ```
448
- */
449
-
450
- /**
451
- * Filters DeepKey<T> to only include paths that resolve to type U.
452
- *
453
- * Uses mapped type with conditional filtering - maps over all possible paths,
454
- * keeps those matching the target type, and extracts the union.
455
- *
456
- * @example Custom depth — propagates to DeepKey
457
- * ```typescript
458
- * // Only number paths, limited to 10 levels deep
459
- * type ShallowNumbers = DeepKeyFiltered<State, number, 10>
460
- * ```
461
- */
462
- type DeepKeyFiltered<T, U, Depth extends number = DefaultDepth> = {
463
- [K in ResolvableDeepKey<T, Depth>]: NonNullable<DeepValue<T, K>> extends U ? K : never;
464
- }[ResolvableDeepKey<T, Depth>];
465
-
466
487
  /**
467
488
  * PathsOfSameValue - Type-safe path tuples for side effects
468
489
  *