@lucas-barake/effect-form 0.13.0 → 0.15.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.
Files changed (62) hide show
  1. package/dist/cjs/Field.js +0 -67
  2. package/dist/cjs/Field.js.map +1 -1
  3. package/dist/cjs/FormAtoms.js +6 -26
  4. package/dist/cjs/FormAtoms.js.map +1 -1
  5. package/dist/cjs/FormBuilder.js +0 -66
  6. package/dist/cjs/FormBuilder.js.map +1 -1
  7. package/dist/cjs/Mode.js +0 -9
  8. package/dist/cjs/Mode.js.map +1 -1
  9. package/dist/cjs/Path.js +0 -34
  10. package/dist/cjs/Path.js.map +1 -1
  11. package/dist/cjs/Validation.js +0 -30
  12. package/dist/cjs/Validation.js.map +1 -1
  13. package/dist/cjs/internal/dirty.js +0 -16
  14. package/dist/cjs/internal/dirty.js.map +1 -1
  15. package/dist/cjs/internal/weak-registry.js +0 -11
  16. package/dist/cjs/internal/weak-registry.js.map +1 -1
  17. package/dist/dts/Field.d.ts +0 -99
  18. package/dist/dts/Field.d.ts.map +1 -1
  19. package/dist/dts/FormAtoms.d.ts +1 -54
  20. package/dist/dts/FormAtoms.d.ts.map +1 -1
  21. package/dist/dts/FormBuilder.d.ts +3 -152
  22. package/dist/dts/FormBuilder.d.ts.map +1 -1
  23. package/dist/dts/Mode.d.ts +0 -33
  24. package/dist/dts/Mode.d.ts.map +1 -1
  25. package/dist/dts/Path.d.ts +0 -34
  26. package/dist/dts/Path.d.ts.map +1 -1
  27. package/dist/dts/Validation.d.ts +0 -37
  28. package/dist/dts/Validation.d.ts.map +1 -1
  29. package/dist/dts/index.d.ts +17 -19
  30. package/dist/dts/index.d.ts.map +1 -1
  31. package/dist/dts/internal/dirty.d.ts +0 -10
  32. package/dist/dts/internal/dirty.d.ts.map +1 -1
  33. package/dist/dts/internal/weak-registry.d.ts +8 -6
  34. package/dist/dts/internal/weak-registry.d.ts.map +1 -1
  35. package/dist/esm/Field.js +0 -66
  36. package/dist/esm/Field.js.map +1 -1
  37. package/dist/esm/FormAtoms.js +7 -27
  38. package/dist/esm/FormAtoms.js.map +1 -1
  39. package/dist/esm/FormBuilder.js +0 -66
  40. package/dist/esm/FormBuilder.js.map +1 -1
  41. package/dist/esm/Mode.js +0 -8
  42. package/dist/esm/Mode.js.map +1 -1
  43. package/dist/esm/Path.js +0 -34
  44. package/dist/esm/Path.js.map +1 -1
  45. package/dist/esm/Validation.js +0 -29
  46. package/dist/esm/Validation.js.map +1 -1
  47. package/dist/esm/index.js +17 -19
  48. package/dist/esm/index.js.map +1 -1
  49. package/dist/esm/internal/dirty.js +0 -15
  50. package/dist/esm/internal/dirty.js.map +1 -1
  51. package/dist/esm/internal/weak-registry.js +0 -11
  52. package/dist/esm/internal/weak-registry.js.map +1 -1
  53. package/package.json +1 -1
  54. package/src/Field.ts +0 -99
  55. package/src/FormAtoms.ts +277 -320
  56. package/src/FormBuilder.ts +0 -172
  57. package/src/Mode.ts +0 -33
  58. package/src/Path.ts +0 -35
  59. package/src/Validation.ts +0 -41
  60. package/src/index.ts +22 -19
  61. package/src/internal/dirty.ts +0 -15
  62. package/src/internal/weak-registry.ts +0 -17
@@ -14,72 +14,31 @@ import type {
14
14
  } from "./Field.js"
15
15
  import { isArrayFieldDef, isFieldDef, makeField } from "./Field.js"
16
16
 
17
- /**
18
- * @category Models
19
- */
20
17
  export interface SubmittedValues<TFields extends FieldsRecord> {
21
18
  readonly encoded: EncodedFromFields<TFields>
22
19
  readonly decoded: DecodedFromFields<TFields>
23
20
  }
24
21
 
25
- /**
26
- * Unique identifier for Field references.
27
- *
28
- * @category Symbols
29
- * @internal
30
- */
31
22
  export const FieldTypeId: unique symbol = Symbol.for("@lucas-barake/effect-form/Field")
32
23
 
33
- /**
34
- * @category Symbols
35
- * @internal
36
- */
37
24
  export type FieldTypeId = typeof FieldTypeId
38
25
 
39
- /**
40
- * A field reference carrying type and path info for type-safe setValue operations.
41
- *
42
- * @category Models
43
- */
44
26
  export interface FieldRef<S> {
45
27
  readonly [FieldTypeId]: FieldTypeId
46
28
  readonly _S: S
47
29
  readonly key: string
48
30
  }
49
31
 
50
- /**
51
- * Creates a field reference for type-safe setValue operations.
52
- *
53
- * @category Constructors
54
- * @internal
55
- */
56
32
  export const makeFieldRef = <S>(key: string): FieldRef<S> => ({
57
33
  [FieldTypeId]: FieldTypeId,
58
34
  _S: undefined as any,
59
35
  key,
60
36
  })
61
37
 
62
- // ================================
63
- // FormBuilder
64
- // ================================
65
-
66
- /**
67
- * Unique identifier for FormBuilder instances.
68
- *
69
- * @category Symbols
70
- */
71
38
  export const TypeId: unique symbol = Symbol.for("@lucas-barake/effect-form/Form")
72
39
 
73
- /**
74
- * @category Symbols
75
- */
76
40
  export type TypeId = typeof TypeId
77
41
 
78
- /**
79
- * The state of a form at runtime.
80
- *
81
- * @category Models
82
- */
83
42
  export interface FormState<TFields extends FieldsRecord> {
84
43
  readonly values: EncodedFromFields<TFields>
85
44
  readonly initialValues: EncodedFromFields<TFields>
@@ -101,123 +60,38 @@ interface AsyncRefinement {
101
60
 
102
61
  type Refinement = SyncRefinement | AsyncRefinement
103
62
 
104
- /**
105
- * A builder for constructing type-safe forms with Effect Schema validation.
106
- *
107
- * **Details**
108
- *
109
- * FormBuilder uses a fluent API pattern to define form fields. Each field
110
- * includes a Schema for validation. The builder accumulates field definitions
111
- * and context requirements (`R`) from schemas that use Effect services.
112
- *
113
- * @category Models
114
- */
115
63
  export interface FormBuilder<TFields extends FieldsRecord, R> {
116
64
  readonly [TypeId]: TypeId
117
65
  readonly fields: TFields
118
66
  readonly refinements: ReadonlyArray<Refinement>
119
67
  readonly _R?: R
120
68
 
121
- /**
122
- * Adds a scalar field to the form builder.
123
- *
124
- * @example
125
- * ```ts
126
- * const NameField = Field.makeField("name", Schema.String)
127
- * const form = FormBuilder.empty.addField(NameField)
128
- * ```
129
- */
130
69
  addField<K extends string, S extends Schema.Schema.Any>(
131
70
  this: FormBuilder<TFields, R>,
132
71
  field: FieldDef<K, S>,
133
72
  ): FormBuilder<TFields & { readonly [key in K]: FieldDef<K, S> }, R | Schema.Schema.Context<S>>
134
73
 
135
- /**
136
- * Adds an array field to the form builder.
137
- *
138
- * @example
139
- * ```ts
140
- * const ItemsField = Field.makeArrayField("items", Schema.Struct({ name: Schema.String }))
141
- * const form = FormBuilder.empty.addField(ItemsField)
142
- * ```
143
- */
144
74
  addField<K extends string, S extends Schema.Schema.Any>(
145
75
  this: FormBuilder<TFields, R>,
146
76
  field: ArrayFieldDef<K, S>,
147
77
  ): FormBuilder<TFields & { readonly [key in K]: ArrayFieldDef<K, S> }, R | Schema.Schema.Context<S>>
148
78
 
149
- /**
150
- * Adds a scalar field using inline key and schema.
151
- * Shorthand for `Field.makeField(key, schema)` when field reuse is not needed.
152
- *
153
- * @example
154
- * ```ts
155
- * const form = FormBuilder.empty
156
- * .addField("email", Schema.String)
157
- * .addField("age", Schema.Number)
158
- * ```
159
- */
160
79
  addField<K extends string, S extends Schema.Schema.Any>(
161
80
  this: FormBuilder<TFields, R>,
162
81
  key: K,
163
82
  schema: S,
164
83
  ): FormBuilder<TFields & { readonly [key in K]: FieldDef<K, S> }, R | Schema.Schema.Context<S>>
165
84
 
166
- /**
167
- * Merges another FormBuilder's fields into this one.
168
- * Useful for composing reusable field groups.
169
- *
170
- * @example
171
- * ```ts
172
- * const addressFields = FormBuilder.empty
173
- * .addField("street", Schema.String)
174
- * .addField("city", Schema.String)
175
- *
176
- * const userForm = FormBuilder.empty
177
- * .addField("name", Schema.String)
178
- * .merge(addressFields)
179
- * ```
180
- */
181
85
  merge<TFields2 extends FieldsRecord, R2>(
182
86
  this: FormBuilder<TFields, R>,
183
87
  other: FormBuilder<TFields2, R2>,
184
88
  ): FormBuilder<TFields & TFields2, R | R2>
185
89
 
186
- /**
187
- * Adds a synchronous cross-field validation refinement to the form.
188
- *
189
- * @example
190
- * ```ts
191
- * const form = FormBuilder.empty
192
- * .addField("password", Schema.String)
193
- * .addField("confirmPassword", Schema.String)
194
- * .refine((values) => {
195
- * if (values.password !== values.confirmPassword) {
196
- * return { path: ["confirmPassword"], message: "Passwords must match" }
197
- * }
198
- * })
199
- * ```
200
- */
201
90
  refine(
202
91
  this: FormBuilder<TFields, R>,
203
92
  predicate: (values: DecodedFromFields<TFields>) => Schema.FilterOutput,
204
93
  ): FormBuilder<TFields, R>
205
94
 
206
- /**
207
- * Adds an asynchronous cross-field validation refinement to the form.
208
- *
209
- * @example
210
- * ```ts
211
- * const form = FormBuilder.empty
212
- * .addField("username", Schema.String)
213
- * .refineEffect((values) =>
214
- * Effect.gen(function* () {
215
- * const taken = yield* checkUsername(values.username)
216
- * if (taken) return { path: ["username"], message: "Already taken" }
217
- * })
218
- * )
219
- * ```
220
- */
221
95
  refineEffect<RD>(
222
96
  this: FormBuilder<TFields, R>,
223
97
  predicate: (values: DecodedFromFields<TFields>) => Effect.Effect<Schema.FilterOutput, never, RD>,
@@ -274,49 +148,8 @@ const FormBuilderProto = {
274
148
  },
275
149
  }
276
150
 
277
- /**
278
- * Checks if a value is a `FormBuilder`.
279
- *
280
- * @example
281
- * ```ts
282
- * import { FormBuilder } from "@lucas-barake/effect-form"
283
- *
284
- * const builder = FormBuilder.empty
285
- *
286
- * console.log(FormBuilder.isFormBuilder(builder))
287
- * // Output: true
288
- *
289
- * console.log(FormBuilder.isFormBuilder({}))
290
- * // Output: false
291
- * ```
292
- *
293
- * @category Guards
294
- */
295
151
  export const isFormBuilder = (u: unknown): u is FormBuilder<any, any> => Predicate.hasProperty(u, TypeId)
296
152
 
297
- /**
298
- * An empty `FormBuilder` to start building a form.
299
- *
300
- * **Details**
301
- *
302
- * This is the entry point for building a form. Use method chaining to add
303
- * fields and then build the form with a React adapter.
304
- *
305
- * @example
306
- * ```ts
307
- * import { Field, FormBuilder } from "@lucas-barake/effect-form"
308
- * import * as Schema from "effect/Schema"
309
- *
310
- * const EmailField = Field.makeField("email", Schema.String)
311
- * const PasswordField = Field.makeField("password", Schema.String)
312
- *
313
- * const loginForm = FormBuilder.empty
314
- * .addField(EmailField)
315
- * .addField(PasswordField)
316
- * ```
317
- *
318
- * @category Constructors
319
- */
320
153
  // eslint-disable-next-line @typescript-eslint/no-empty-object-type
321
154
  export const empty: FormBuilder<{}, never> = (() => {
322
155
  const self = Object.create(FormBuilderProto)
@@ -325,11 +158,6 @@ export const empty: FormBuilder<{}, never> = (() => {
325
158
  return self
326
159
  })()
327
160
 
328
- /**
329
- * Builds a combined Schema from a FormBuilder's field definitions.
330
- *
331
- * @category Schema
332
- */
333
161
  export const buildSchema = <TFields extends FieldsRecord, R>(
334
162
  self: FormBuilder<TFields, R>,
335
163
  ): Schema.Schema<DecodedFromFields<TFields>, EncodedFromFields<TFields>, R> => {
package/src/Mode.ts CHANGED
@@ -1,22 +1,5 @@
1
- /**
2
- * Form validation mode configuration.
3
- */
4
1
  import * as Duration from "effect/Duration"
5
2
 
6
- /**
7
- * Controls when field validation is triggered and whether form auto-submits.
8
- *
9
- * Simple modes (string):
10
- * - `"onSubmit"`: Validation only runs when the form is submitted (default)
11
- * - `"onBlur"`: Validation runs when a field loses focus
12
- * - `"onChange"`: Validation runs on every value change (sync)
13
- *
14
- * Object modes (with options):
15
- * - `{ onChange: { debounce, autoSubmit? } }`: Debounced validation, optional auto-submit
16
- * - `{ onBlur: { autoSubmit: true } }`: Validate on blur, auto-submit when valid
17
- *
18
- * @category Models
19
- */
20
3
  export type FormMode =
21
4
  | "onSubmit"
22
5
  | "onBlur"
@@ -25,34 +8,18 @@ export type FormMode =
25
8
  | { readonly onBlur: { readonly autoSubmit: true } }
26
9
  | { readonly onChange: { readonly debounce: Duration.DurationInput; readonly autoSubmit: true } }
27
10
 
28
- /**
29
- * Form mode without auto-submit options.
30
- * Used when SubmitArgs is not void, since auto-submit cannot provide custom arguments.
31
- *
32
- * @category Models
33
- */
34
11
  export type FormModeWithoutAutoSubmit =
35
12
  | "onSubmit"
36
13
  | "onBlur"
37
14
  | "onChange"
38
15
  | { readonly onChange: { readonly debounce: Duration.DurationInput; readonly autoSubmit?: false } }
39
16
 
40
- /**
41
- * Parsed form mode with resolved values.
42
- *
43
- * @category Models
44
- */
45
17
  export interface ParsedMode {
46
18
  readonly validation: "onSubmit" | "onBlur" | "onChange"
47
19
  readonly debounce: number | null
48
20
  readonly autoSubmit: boolean
49
21
  }
50
22
 
51
- /**
52
- * Parses a FormMode into a normalized ParsedMode.
53
- *
54
- * @category Parsing
55
- */
56
23
  export const parse = (mode: FormMode = "onSubmit"): ParsedMode => {
57
24
  if (typeof mode === "string") {
58
25
  return { validation: mode, debounce: null, autoSubmit: false }
package/src/Path.ts CHANGED
@@ -1,15 +1,5 @@
1
- /**
2
- * Path utilities for form field operations.
3
- */
4
-
5
1
  const BRACKET_NOTATION_REGEX = /\[(\d+)\]/g
6
2
 
7
- /**
8
- * Converts a schema path array to a dot/bracket notation string.
9
- *
10
- * @example
11
- * schemaPathToFieldPath(["items", 0, "name"]) // "items[0].name"
12
- */
13
3
  export const schemaPathToFieldPath = (path: ReadonlyArray<PropertyKey>): string => {
14
4
  if (path.length === 0) return ""
15
5
 
@@ -25,21 +15,9 @@ export const schemaPathToFieldPath = (path: ReadonlyArray<PropertyKey>): string
25
15
  return result
26
16
  }
27
17
 
28
- /**
29
- * Checks if a path matches a root path or is a descendant of it.
30
- * Handles both dot notation (root.child) and bracket notation (root[0]).
31
- *
32
- * @example
33
- * isPathUnderRoot("items[0].name", "items[0]") // true
34
- * isPathUnderRoot("items[0].name", "items") // true
35
- * isPathUnderRoot("other", "items") // false
36
- */
37
18
  export const isPathUnderRoot = (path: string, rootPath: string): boolean =>
38
19
  path === rootPath || path.startsWith(rootPath + ".") || path.startsWith(rootPath + "[")
39
20
 
40
- /**
41
- * Checks if a field path or any of its parent paths are in the dirty set.
42
- */
43
21
  export const isPathOrParentDirty = (dirtyFields: ReadonlySet<string>, path: string): boolean => {
44
22
  if (dirtyFields.has(path)) return true
45
23
 
@@ -58,12 +36,6 @@ export const isPathOrParentDirty = (dirtyFields: ReadonlySet<string>, path: stri
58
36
  return false
59
37
  }
60
38
 
61
- /**
62
- * Gets a nested value from an object using dot/bracket notation path.
63
- *
64
- * @example
65
- * getNestedValue({ items: [{ name: "A" }] }, "items[0].name") // "A"
66
- */
67
39
  export const getNestedValue = (obj: unknown, path: string): unknown => {
68
40
  if (path === "") return obj
69
41
  const parts = path.replace(BRACKET_NOTATION_REGEX, ".$1").split(".")
@@ -75,13 +47,6 @@ export const getNestedValue = (obj: unknown, path: string): unknown => {
75
47
  return current
76
48
  }
77
49
 
78
- /**
79
- * Sets a nested value in an object immutably using dot/bracket notation path.
80
- *
81
- * @example
82
- * setNestedValue({ items: [{ name: "A" }] }, "items[0].name", "B")
83
- * // { items: [{ name: "B" }] }
84
- */
85
50
  export const setNestedValue = <T>(obj: T, path: string, value: unknown): T => {
86
51
  if (path === "") return value as T
87
52
  const parts = path.replace(BRACKET_NOTATION_REGEX, ".$1").split(".")
package/src/Validation.ts CHANGED
@@ -1,25 +1,10 @@
1
- /**
2
- * Validation utilities for form error handling.
3
- */
4
1
  import * as Option from "effect/Option"
5
2
  import * as ParseResult from "effect/ParseResult"
6
3
  import type * as AST from "effect/SchemaAST"
7
4
  import { schemaPathToFieldPath } from "./Path.js"
8
5
 
9
- /**
10
- * Source of a validation error.
11
- * - 'field': Error from field schema validation (e.g., minLength, pattern)
12
- * - 'refinement': Error from cross-field refinement (e.g., password !== confirm)
13
- *
14
- * @category Models
15
- */
16
6
  export type ErrorSource = "field" | "refinement"
17
7
 
18
- /**
19
- * A validation error entry with its source.
20
- *
21
- * @category Models
22
- */
23
8
  export interface ErrorEntry {
24
9
  readonly message: string
25
10
  readonly source: ErrorSource
@@ -35,10 +20,6 @@ const getBaseAST = (ast: AST.AST): AST.AST => {
35
20
  }
36
21
  }
37
22
 
38
- /**
39
- * Returns true if the AST represents a composite type where refinements indicate cross-field validation.
40
- * Covers: Schema.Struct, Class, Tuple, Union, Suspend.
41
- */
42
23
  const isCompositeType = (ast: AST.AST): boolean => {
43
24
  const base = getBaseAST(ast)
44
25
  switch (base._tag) {
@@ -53,11 +34,6 @@ const isCompositeType = (ast: AST.AST): boolean => {
53
34
  }
54
35
  }
55
36
 
56
- /**
57
- * Extracts the first error message from a ParseError.
58
- *
59
- * @category Error Handling
60
- */
61
37
  export const extractFirstError = (error: ParseResult.ParseError): Option.Option<string> => {
62
38
  const issues = ParseResult.ArrayFormatter.formatErrorSync(error)
63
39
  if (issues.length === 0) {
@@ -66,12 +42,6 @@ export const extractFirstError = (error: ParseResult.ParseError): Option.Option<
66
42
  return Option.some(issues[0].message)
67
43
  }
68
44
 
69
- /**
70
- * Routes validation errors from a ParseError to a map of field paths to error messages.
71
- * Used for cross-field validation where schema errors need to be displayed on specific fields.
72
- *
73
- * @category Error Handling
74
- */
75
45
  export const routeErrors = (error: ParseResult.ParseError): Map<string, string> => {
76
46
  const result = new Map<string, string>()
77
47
  const issues = ParseResult.ArrayFormatter.formatErrorSync(error)
@@ -140,17 +110,6 @@ const determineErrorSources = (error: ParseResult.ParseError): Map<string, Error
140
110
  return sources
141
111
  }
142
112
 
143
- /**
144
- * Routes validation errors with source tracking.
145
- *
146
- * Source determination:
147
- * - `kind: "Predicate"` on composite types → "refinement" (cross-field validation)
148
- * - All other errors → "field" (per-field schema validation)
149
- *
150
- * Empty string key ("") stores root-level errors (refinements without specific paths).
151
- *
152
- * @category Error Handling
153
- */
154
113
  export const routeErrorsWithSource = (error: ParseResult.ParseError): Map<string, ErrorEntry> => {
155
114
  const result = new Map<string, ErrorEntry>()
156
115
  const formattedIssues = ParseResult.ArrayFormatter.formatErrorSync(error)
package/src/index.ts CHANGED
@@ -1,31 +1,34 @@
1
- /**
2
- * Field definitions for type-safe forms.
3
- */
1
+
4
2
  export * as Field from "./Field.js"
5
3
 
6
4
  /**
7
- * Atoms for a single field.
8
- *
9
- * @category Models
10
- */
5
+ * Root anchor atom for the form's dependency graph.
6
+ * Mount this atom to keep all form state alive even when field components unmount.
7
+ *
8
+ * Useful for:
9
+ * - Multi-step wizards where steps unmount but state should persist
10
+ * - Conditional fields (toggles) where state should survive visibility changes
11
+ *
12
+ * @example
13
+ * ```tsx
14
+ * // Keep form state alive at wizard root level
15
+ * function Wizard() {
16
+ * useAtomMount(step1Form.mount)
17
+ * useAtomMount(step2Form.mount)
18
+ * return currentStep === 1 ? <Step1 /> : <Step2 />
19
+ * }
20
+ * ```
21
+ */
11
22
  export * as FormAtoms from "./FormAtoms.js"
12
23
 
13
- /**
14
- * @category Models
15
- */
24
+
16
25
  export * as FormBuilder from "./FormBuilder.js"
17
26
 
18
- /**
19
- * Form validation mode configuration.
20
- */
27
+
21
28
  export * as Mode from "./Mode.js"
22
29
 
23
- /**
24
- * Path utilities for form field operations.
25
- */
30
+
26
31
  export * as Path from "./Path.js"
27
32
 
28
- /**
29
- * Validation utilities for form error handling.
30
- */
33
+
31
34
  export * as Validation from "./Validation.js"
@@ -1,16 +1,7 @@
1
- /**
2
- * Internal dirty tracking algorithms.
3
- *
4
- * @internal
5
- */
6
1
  import * as Equal from "effect/Equal"
7
2
  import * as Utils from "effect/Utils"
8
3
  import { getNestedValue, isPathUnderRoot } from "../Path.js"
9
4
 
10
- /**
11
- * Recalculates dirty fields for an array after mutation.
12
- * Clears all paths under the array and re-evaluates each item.
13
- */
14
5
  export const recalculateDirtyFieldsForArray = (
15
6
  dirtyFields: ReadonlySet<string>,
16
7
  initialValues: unknown,
@@ -50,12 +41,6 @@ export const recalculateDirtyFieldsForArray = (
50
41
  return nextDirty
51
42
  }
52
43
 
53
- /**
54
- * Recalculates dirty fields for a subtree after value change.
55
- * Clears the rootPath and all children, then re-evaluates recursively.
56
- *
57
- * @param rootPath - Empty string for full form, or a specific path for targeted update
58
- */
59
44
  export const recalculateDirtySubtree = (
60
45
  currentDirty: ReadonlySet<string>,
61
46
  allInitial: unknown,
@@ -1,14 +1,3 @@
1
- /**
2
- * Internal WeakRef-based registry for caching atoms.
3
- *
4
- * @internal
5
- */
6
-
7
- /**
8
- * A registry that uses WeakRef to allow garbage collection of cached values.
9
- *
10
- * @internal
11
- */
12
1
  export interface WeakRegistry<V extends object> {
13
2
  readonly get: (key: string) => V | undefined
14
3
  readonly set: (key: string, value: V) => void
@@ -17,12 +6,6 @@ export interface WeakRegistry<V extends object> {
17
6
  readonly values: () => IterableIterator<V>
18
7
  }
19
8
 
20
- /**
21
- * Creates a WeakRef-based registry with automatic cleanup via FinalizationRegistry.
22
- * Falls back to a regular Map in environments without WeakRef support.
23
- *
24
- * @internal
25
- */
26
9
  export const createWeakRegistry = <V extends object>(): WeakRegistry<V> => {
27
10
  if (typeof WeakRef === "undefined" || typeof FinalizationRegistry === "undefined") {
28
11
  const map = new Map<string, V>()