@kontsedal/olas-core 0.0.1-rc.1 → 0.0.2
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.cjs +40 -10
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +32 -11
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +32 -11
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +40 -11
- package/dist/index.mjs.map +1 -1
- package/dist/{root-BImHnGj1.mjs → root-De-6KWIZ.mjs} +750 -149
- package/dist/root-De-6KWIZ.mjs.map +1 -0
- package/dist/{root-Bazp5_Ik.cjs → root-XKEsSmcd.cjs} +755 -148
- package/dist/root-XKEsSmcd.cjs.map +1 -0
- package/dist/testing.cjs +1 -1
- package/dist/testing.d.cts +1 -1
- package/dist/testing.d.mts +1 -1
- package/dist/testing.mjs +1 -1
- package/dist/{types-CAMgqCMz.d.mts → types-C-zV1JZA.d.mts} +215 -13
- package/dist/types-C-zV1JZA.d.mts.map +1 -0
- package/dist/{types-emq_lZd7.d.cts → types-DKfpkm17.d.cts} +215 -13
- package/dist/types-DKfpkm17.d.cts.map +1 -0
- package/package.json +1 -1
- package/src/controller/index.ts +6 -0
- package/src/controller/instance.ts +432 -18
- package/src/controller/root.ts +9 -1
- package/src/controller/types.ts +148 -7
- package/src/emitter.ts +34 -3
- package/src/forms/field.ts +73 -8
- package/src/forms/form-types.ts +16 -0
- package/src/forms/form.ts +218 -26
- package/src/index.ts +12 -1
- package/src/query/client.ts +161 -6
- package/src/query/define.ts +14 -0
- package/src/query/entry.ts +64 -42
- package/src/query/infinite.ts +77 -55
- package/src/query/mutation.ts +11 -21
- package/src/query/plugin.ts +50 -0
- package/src/query/use.ts +80 -3
- package/src/signals/readonly.ts +3 -3
- package/src/timing/debounced.ts +24 -4
- package/src/timing/throttled.ts +22 -3
- package/src/utils.ts +32 -4
- package/dist/root-BImHnGj1.mjs.map +0 -1
- package/dist/root-Bazp5_Ik.cjs.map +0 -1
- package/dist/types-CAMgqCMz.d.mts.map +0 -1
- package/dist/types-emq_lZd7.d.cts.map +0 -1
package/src/controller/types.ts
CHANGED
|
@@ -79,6 +79,80 @@ export type CtrlProps<C> = C extends ControllerDef<infer P, unknown> ? P : never
|
|
|
79
79
|
/** Extract a controller's Api type. */
|
|
80
80
|
export type CtrlApi<C> = C extends ControllerDef<unknown, infer A> ? A : never
|
|
81
81
|
|
|
82
|
+
/**
|
|
83
|
+
* The reactive surface returned by `ctx.collection(...)`. `items` is the
|
|
84
|
+
* canonical ordered view (source-order, with any construction-failed items
|
|
85
|
+
* filtered out); `size` mirrors `items.length`; `get` / `has` are
|
|
86
|
+
* imperative key lookups. SPEC §11.1.
|
|
87
|
+
*/
|
|
88
|
+
export type Collection<K, Api> = {
|
|
89
|
+
readonly items: ReadSignal<ReadonlyArray<{ readonly key: K; readonly api: Api }>>
|
|
90
|
+
readonly size: ReadSignal<number>
|
|
91
|
+
get(key: K): Api | undefined
|
|
92
|
+
has(key: K): boolean
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Homogeneous form of `ctx.collection`: one controller def for every item,
|
|
97
|
+
* with `propsOf` projecting each item to the controller's `Props`. Construct
|
|
98
|
+
* happens once per new key — `propsOf` is **not** re-applied for unchanged
|
|
99
|
+
* keys.
|
|
100
|
+
*/
|
|
101
|
+
export type CollectionHomogeneousOptions<Item, K, Props, Api, TDeps = AmbientDeps> = {
|
|
102
|
+
readonly source: ReadSignal<readonly Item[]>
|
|
103
|
+
readonly keyOf: (item: Item) => K
|
|
104
|
+
readonly controller: ControllerDef<Props, Api>
|
|
105
|
+
readonly propsOf: (item: Item) => Props
|
|
106
|
+
readonly factory?: never
|
|
107
|
+
readonly propsFor?: never
|
|
108
|
+
readonly deps?: Partial<TDeps>
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Heterogeneous form of `ctx.collection`: a single `factory` decides per-item
|
|
113
|
+
* which controller + props to construct. When a key's factory result picks a
|
|
114
|
+
* *different* controller than last time, the existing child is disposed and
|
|
115
|
+
* the new one constructed (type-discriminant rebuild).
|
|
116
|
+
*
|
|
117
|
+
* `R` is the factory's *return type* (typically inferred as the union of the
|
|
118
|
+
* branches' `{ controller, props }` shapes). `Api` is then projected out as
|
|
119
|
+
* the union of every branch's controller Api via `CollectionFactoryApi<R>` —
|
|
120
|
+
* unlike a single `Api` generic, the union doesn't collapse to the first
|
|
121
|
+
* branch.
|
|
122
|
+
*/
|
|
123
|
+
export type CollectionFactoryOptions<Item, K, R, TDeps = AmbientDeps> = {
|
|
124
|
+
readonly source: ReadSignal<readonly Item[]>
|
|
125
|
+
readonly keyOf: (item: Item) => K
|
|
126
|
+
readonly controller?: never
|
|
127
|
+
readonly propsOf?: never
|
|
128
|
+
readonly factory: (item: Item) => R
|
|
129
|
+
readonly deps?: Partial<TDeps>
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/** Constraint for the factory form's return shape. */
|
|
133
|
+
// biome-ignore lint/suspicious/noExplicitAny: per-branch types vary
|
|
134
|
+
export type CollectionFactoryResult = { controller: ControllerDef<any, any>; props: any }
|
|
135
|
+
|
|
136
|
+
/** Extract the union of every branch's controller Api. Distributes over R. */
|
|
137
|
+
export type CollectionFactoryApi<R> = R extends {
|
|
138
|
+
// biome-ignore lint/suspicious/noExplicitAny: distributive infer across the union
|
|
139
|
+
controller: ControllerDef<any, infer A>
|
|
140
|
+
}
|
|
141
|
+
? A
|
|
142
|
+
: never
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Handle returned by `ctx.lazyChild(...)`. `status` walks `idle → loading →
|
|
146
|
+
* (ready | error)`; `api` becomes defined once `status === 'ready'`. SPEC §16.5.
|
|
147
|
+
*/
|
|
148
|
+
export type LazyChild<Api> = {
|
|
149
|
+
readonly status: ReadSignal<'idle' | 'loading' | 'ready' | 'error'>
|
|
150
|
+
readonly api: ReadSignal<Api | undefined>
|
|
151
|
+
readonly error: ReadSignal<unknown | undefined>
|
|
152
|
+
load(): Promise<Api>
|
|
153
|
+
dispose(): void
|
|
154
|
+
}
|
|
155
|
+
|
|
82
156
|
/**
|
|
83
157
|
* `ctx` is the lifecycle-bound surface every controller factory receives.
|
|
84
158
|
* Every primitive constructed through `ctx` is owned by the controller and
|
|
@@ -127,18 +201,85 @@ export type Ctx<TDeps = AmbientDeps> = {
|
|
|
127
201
|
): Api
|
|
128
202
|
|
|
129
203
|
/**
|
|
130
|
-
* Like `child(...)` but additionally returns a
|
|
131
|
-
*
|
|
132
|
-
*
|
|
133
|
-
*
|
|
134
|
-
*
|
|
135
|
-
*
|
|
204
|
+
* Like `child(...)` but additionally returns a handle that lets the parent
|
|
205
|
+
* control the attached sub-tree's lifecycle independently — `dispose()`
|
|
206
|
+
* tears it down early, and `suspend()` / `resume()` freeze and thaw it.
|
|
207
|
+
* The child is still disposed automatically when the parent disposes;
|
|
208
|
+
* `dispose()` / `suspend()` / `resume()` are idempotent.
|
|
209
|
+
*
|
|
210
|
+
* `<KeepAlive controller={…}>` in `@kontsedal/olas-react` consumes the
|
|
211
|
+
* returned `{ suspend, resume }` directly — no hand-rolled `isPaused`
|
|
212
|
+
* signal needed on the child's `Api`. Useful for "openable" sub-
|
|
213
|
+
* controllers driven by a user gesture (modal, side panel, wizard).
|
|
214
|
+
*
|
|
215
|
+
* `suspend()` cascades through the attached controller's lifecycle
|
|
216
|
+
* entries: cache subscriptions pause `refetchInterval` and release the
|
|
217
|
+
* entry, effects are torn down, `onSuspend(...)` handlers fire. `resume()`
|
|
218
|
+
* re-runs effects, re-acquires cache entries (a stale entry refetches),
|
|
219
|
+
* and fires `onResume(...)`. Spec §4.1, §16.5.
|
|
136
220
|
*/
|
|
137
221
|
attach<Props, Api>(
|
|
138
222
|
def: ControllerDef<Props, Api>,
|
|
139
223
|
props: Props,
|
|
140
224
|
options?: { deps?: Partial<TDeps> },
|
|
141
|
-
): { api: Api; dispose: () => void }
|
|
225
|
+
): { api: Api; dispose: () => void; suspend: () => void; resume: () => void }
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Ephemeral child controller bound to either (a) the explicit `dispose()`
|
|
229
|
+
* call returned in the tuple, or (b) the parent's disposal — whichever
|
|
230
|
+
* comes first. Same lifecycle semantics as `ctx.attach` minus suspend /
|
|
231
|
+
* resume (sessions are short-lived, not pause-able). Returns a `[api,
|
|
232
|
+
* dispose]` tuple so the api shape is exactly the controller's return
|
|
233
|
+
* type, with no wrapper to unpack.
|
|
234
|
+
*
|
|
235
|
+
* Use cases: modal forms, inline edit sessions, wizards, command palette.
|
|
236
|
+
* SPEC §11.1.
|
|
237
|
+
*/
|
|
238
|
+
session<Props, Api>(
|
|
239
|
+
def: ControllerDef<Props, Api>,
|
|
240
|
+
props: Props,
|
|
241
|
+
options?: { deps?: Partial<TDeps> },
|
|
242
|
+
): readonly [api: Api, dispose: () => void]
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Diff-by-key set of child controllers driven by a reactive `source`.
|
|
246
|
+
* On every change to `source`, the collection:
|
|
247
|
+
* - **new keys** → construct a child via `controller` + `propsOf(item)`
|
|
248
|
+
* (or `factory(item)` for the heterogeneous form);
|
|
249
|
+
* - **removed keys** → dispose that child;
|
|
250
|
+
* - **unchanged keys** → leave it alone (`propsOf` is NOT re-applied).
|
|
251
|
+
*
|
|
252
|
+
* For per-item type-discriminated children, use the `factory` form —
|
|
253
|
+
* type changes for an existing key dispose and reconstruct.
|
|
254
|
+
*
|
|
255
|
+
* Construction errors (factory or controller throw) are routed to
|
|
256
|
+
* `onError` with `kind: 'construction'` and the item is **skipped** —
|
|
257
|
+
* the collection's surface shows one fewer entry. The diff loop does
|
|
258
|
+
* not re-throw. SPEC §11.1, §12.1.6.
|
|
259
|
+
*/
|
|
260
|
+
collection<Item, K, Props, Api>(
|
|
261
|
+
options: CollectionHomogeneousOptions<Item, K, Props, Api, TDeps>,
|
|
262
|
+
): Collection<K, Api>
|
|
263
|
+
collection<Item, K, R extends CollectionFactoryResult>(
|
|
264
|
+
options: CollectionFactoryOptions<Item, K, R, TDeps>,
|
|
265
|
+
): Collection<K, CollectionFactoryApi<R>>
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Code-split child controller. The loader is invoked on `load()`
|
|
269
|
+
* (idempotent), then the controller is constructed with the supplied
|
|
270
|
+
* `props`. `status` / `api` / `error` are reactive signals; subscribe
|
|
271
|
+
* via `use(child.api)` in your view layer.
|
|
272
|
+
*
|
|
273
|
+
* Parent disposal disposes the loaded child (if any) and flags any
|
|
274
|
+
* in-flight load so its eventual settle is dropped on the floor.
|
|
275
|
+
* Construction or import failures route through `onError` with
|
|
276
|
+
* `kind: 'construction'`. SPEC §16.5.
|
|
277
|
+
*/
|
|
278
|
+
lazyChild<Props, Api>(
|
|
279
|
+
loader: () => Promise<ControllerDef<Props, Api>>,
|
|
280
|
+
props: Props,
|
|
281
|
+
options?: { deps?: Partial<TDeps> },
|
|
282
|
+
): LazyChild<Api>
|
|
142
283
|
|
|
143
284
|
effect(fn: () => void | (() => void)): void
|
|
144
285
|
|
package/src/emitter.ts
CHANGED
|
@@ -20,17 +20,43 @@ export type Emitter<T> = {
|
|
|
20
20
|
|
|
21
21
|
type AnyHandler = (value: unknown) => void
|
|
22
22
|
|
|
23
|
+
/**
|
|
24
|
+
* Optional escape hatch for emit-time handler throws. If supplied, a thrown
|
|
25
|
+
* handler is reported here and emission continues with the remaining handlers
|
|
26
|
+
* (spec §20.6 — one throwing handler must not block the rest). If absent,
|
|
27
|
+
* the throw is logged via `console.error`.
|
|
28
|
+
*/
|
|
29
|
+
export type EmitterErrorReporter = (err: unknown) => void
|
|
30
|
+
|
|
23
31
|
class EmitterImpl<T> {
|
|
24
32
|
private handlers = new Set<AnyHandler>()
|
|
25
33
|
private disposed = false
|
|
26
34
|
|
|
35
|
+
constructor(private onError?: EmitterErrorReporter) {}
|
|
36
|
+
|
|
27
37
|
emit(value: T): void {
|
|
28
38
|
if (this.disposed) return
|
|
29
39
|
// Snapshot so a handler that unsubscribes itself (or another) doesn't
|
|
30
40
|
// mutate the set mid-iteration.
|
|
31
41
|
const snapshot = Array.from(this.handlers)
|
|
32
42
|
for (const handler of snapshot) {
|
|
33
|
-
|
|
43
|
+
try {
|
|
44
|
+
handler(value as unknown)
|
|
45
|
+
} catch (err) {
|
|
46
|
+
// Spec §20.6: isolate handler throws so siblings still fire.
|
|
47
|
+
if (this.onError) {
|
|
48
|
+
try {
|
|
49
|
+
this.onError(err)
|
|
50
|
+
} catch {
|
|
51
|
+
// Reporter itself threw — last resort.
|
|
52
|
+
// eslint-disable-next-line no-console
|
|
53
|
+
console.error('[olas] emitter handler threw and reporter threw:', err)
|
|
54
|
+
}
|
|
55
|
+
} else {
|
|
56
|
+
// eslint-disable-next-line no-console
|
|
57
|
+
console.error('[olas] emitter handler threw:', err)
|
|
58
|
+
}
|
|
59
|
+
}
|
|
34
60
|
}
|
|
35
61
|
}
|
|
36
62
|
|
|
@@ -67,9 +93,14 @@ class EmitterImpl<T> {
|
|
|
67
93
|
* (or the emitter is disposed). Use this for emitters that live outside any
|
|
68
94
|
* single controller — typically in deps. Use `ctx.emitter()` for emitters that
|
|
69
95
|
* should auto-clean with a controller.
|
|
96
|
+
*
|
|
97
|
+
* Pass `onError` to receive emit-time handler throws (spec §20.6 — one
|
|
98
|
+
* throwing handler must not block the rest of the fan-out). `ctx.emitter()`
|
|
99
|
+
* wires this to the root's `onError` so deps-level emitters get isolation
|
|
100
|
+
* by default when constructed via `ctx`.
|
|
70
101
|
*/
|
|
71
|
-
export function createEmitter<T = void>(): Emitter<T> {
|
|
72
|
-
const impl = new EmitterImpl<T>()
|
|
102
|
+
export function createEmitter<T = void>(options?: { onError?: EmitterErrorReporter }): Emitter<T> {
|
|
103
|
+
const impl = new EmitterImpl<T>(options?.onError)
|
|
73
104
|
return {
|
|
74
105
|
emit: ((value?: T) => impl.emit(value as T)) as Emitter<T>['emit'],
|
|
75
106
|
on: (handler) => impl.on(handler),
|
package/src/forms/field.ts
CHANGED
|
@@ -23,6 +23,17 @@ export type FieldDevtoolsOwner = {
|
|
|
23
23
|
emitter: DevtoolsEmitter
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
+
/**
|
|
27
|
+
* Optional reporter for synchronous validator throws — wired in by `ctx.field`
|
|
28
|
+
* (and `createForm` for leaf fields inside a form) so a thrown validator
|
|
29
|
+
* doesn't escape the signal effect silently. Without this, a buggy validator
|
|
30
|
+
* just stops contributing to `errors` and the field reads as "valid" while
|
|
31
|
+
* silently broken. With it, the throw is routed through `root.onError` as
|
|
32
|
+
* `kind: 'effect'` AND the throw's message lands in the field's `errors`
|
|
33
|
+
* array so the UI surfaces the problem.
|
|
34
|
+
*/
|
|
35
|
+
export type ValidatorErrorReporter = (err: unknown) => void
|
|
36
|
+
|
|
26
37
|
class FieldImpl<T> implements Field<T> {
|
|
27
38
|
private readonly value$: Signal<T>
|
|
28
39
|
private readonly errors$: Signal<string[]>
|
|
@@ -41,10 +52,20 @@ class FieldImpl<T> implements Field<T> {
|
|
|
41
52
|
private runId = 0
|
|
42
53
|
private disposed = false
|
|
43
54
|
private devtoolsOwner: FieldDevtoolsOwner | null = null
|
|
55
|
+
private onValidatorError: ValidatorErrorReporter | null = null
|
|
44
56
|
|
|
45
|
-
constructor(
|
|
57
|
+
constructor(
|
|
58
|
+
initial: T,
|
|
59
|
+
validators: ReadonlyArray<Validator<T>> = [],
|
|
60
|
+
options?: { onValidatorError?: ValidatorErrorReporter },
|
|
61
|
+
) {
|
|
46
62
|
this.initial = initial
|
|
47
63
|
this.validators = validators
|
|
64
|
+
// Capture the reporter BEFORE the validator effect kicks off so a sync
|
|
65
|
+
// throw on the very first pass routes through `onError` instead of
|
|
66
|
+
// disappearing into the effect (`bindValidatorErrorReporter` is a
|
|
67
|
+
// post-construct hook so it can't catch the first run).
|
|
68
|
+
this.onValidatorError = options?.onValidatorError ?? null
|
|
48
69
|
this.value$ = signal(initial)
|
|
49
70
|
this.errors$ = signal<string[]>([])
|
|
50
71
|
this.touched$ = signal(false)
|
|
@@ -60,6 +81,14 @@ class FieldImpl<T> implements Field<T> {
|
|
|
60
81
|
}
|
|
61
82
|
}
|
|
62
83
|
|
|
84
|
+
/**
|
|
85
|
+
* Internal hook for `ctx.field` / `createForm` to route synchronous
|
|
86
|
+
* validator throws through `root.onError`. See `ValidatorErrorReporter`.
|
|
87
|
+
*/
|
|
88
|
+
bindValidatorErrorReporter(reporter: ValidatorErrorReporter | null): void {
|
|
89
|
+
this.onValidatorError = reporter
|
|
90
|
+
}
|
|
91
|
+
|
|
63
92
|
// --- ReadSignal<T> ---
|
|
64
93
|
get value(): T {
|
|
65
94
|
return this.value$.value
|
|
@@ -207,11 +236,27 @@ class FieldImpl<T> implements Field<T> {
|
|
|
207
236
|
const asyncPromises: Promise<string | null>[] = []
|
|
208
237
|
|
|
209
238
|
for (const validator of this.validators) {
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
239
|
+
try {
|
|
240
|
+
const result = validator(value, abort.signal)
|
|
241
|
+
if (result instanceof Promise) {
|
|
242
|
+
// Defend against the validator promise rejecting *synchronously*
|
|
243
|
+
// with a thrown error (rare but legal) — the catch-handler in
|
|
244
|
+
// `Promise.allSettled` covers true async rejection.
|
|
245
|
+
asyncPromises.push(result)
|
|
246
|
+
} else if (result != null) {
|
|
247
|
+
syncErrors.push(result)
|
|
248
|
+
}
|
|
249
|
+
} catch (err) {
|
|
250
|
+
// A buggy validator that throws synchronously: surface it twice.
|
|
251
|
+
// (1) Route through `onError` so the user knows something is wrong.
|
|
252
|
+
// (2) Convert to a validation error string so the field reads invalid
|
|
253
|
+
// until the bug is fixed (don't pretend everything's OK).
|
|
254
|
+
try {
|
|
255
|
+
this.onValidatorError?.(err)
|
|
256
|
+
} catch {
|
|
257
|
+
// The reporter must not propagate.
|
|
258
|
+
}
|
|
259
|
+
syncErrors.push(err instanceof Error ? err.message : String(err))
|
|
215
260
|
}
|
|
216
261
|
}
|
|
217
262
|
|
|
@@ -270,8 +315,28 @@ export function bindFieldDevtoolsOwner<T>(field: Field<T>, owner: FieldDevtoolsO
|
|
|
270
315
|
}
|
|
271
316
|
}
|
|
272
317
|
|
|
273
|
-
|
|
274
|
-
|
|
318
|
+
/**
|
|
319
|
+
* Internal — install a synchronous-validator-throw reporter on a `Field`
|
|
320
|
+
* (matched structurally to keep the public `Field<T>` surface stable).
|
|
321
|
+
* Called by `ctx.field` and `bindTreeToDevtools` so leaves inside a form/
|
|
322
|
+
* field-array tree get the same reporting as a standalone field.
|
|
323
|
+
*/
|
|
324
|
+
export function bindFieldValidatorErrorReporter<T>(
|
|
325
|
+
field: Field<T>,
|
|
326
|
+
reporter: ValidatorErrorReporter | null,
|
|
327
|
+
): void {
|
|
328
|
+
const impl = field as { bindValidatorErrorReporter?: (r: ValidatorErrorReporter | null) => void }
|
|
329
|
+
if (typeof impl.bindValidatorErrorReporter === 'function') {
|
|
330
|
+
impl.bindValidatorErrorReporter(reporter)
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
export function createField<T>(
|
|
335
|
+
initial: T,
|
|
336
|
+
validators?: ReadonlyArray<Validator<T>>,
|
|
337
|
+
options?: { onValidatorError?: ValidatorErrorReporter },
|
|
338
|
+
): Field<T> {
|
|
339
|
+
return new FieldImpl(initial, validators, options)
|
|
275
340
|
}
|
|
276
341
|
|
|
277
342
|
/**
|
package/src/forms/form-types.ts
CHANGED
|
@@ -45,8 +45,24 @@ export type FormValidator<S extends FormSchema> = Validator<FormValue<S>>
|
|
|
45
45
|
export type FieldArrayValidator<I> = Validator<FieldArrayValue<I>>
|
|
46
46
|
|
|
47
47
|
export type FormOptions<S extends FormSchema> = {
|
|
48
|
+
/**
|
|
49
|
+
* Initial values for the form. A function form is **tracked** — if the
|
|
50
|
+
* function reads reactive signals (e.g. a query's `data`), the form re-seats
|
|
51
|
+
* itself when those signals change, but only while the form is not dirty
|
|
52
|
+
* (so a user mid-edit isn't clobbered by a background refetch). See
|
|
53
|
+
* `resetOnInitialChange` for opt-out. Spec §8.4.
|
|
54
|
+
*/
|
|
48
55
|
initial?: (() => DeepPartial<FormValue<S>> | undefined) | DeepPartial<FormValue<S>>
|
|
49
56
|
validators?: FormValidator<S>[]
|
|
57
|
+
/**
|
|
58
|
+
* When `initial` is a function and one of its tracked deps changes:
|
|
59
|
+
* - `'when-clean'` (default) — re-seat only if the form is not dirty.
|
|
60
|
+
* - `'never'` — never re-seat; `initial()` runs once at construction.
|
|
61
|
+
* - `'always'` — re-seat unconditionally (dirty state is discarded).
|
|
62
|
+
*
|
|
63
|
+
* Spec §20.7.
|
|
64
|
+
*/
|
|
65
|
+
resetOnInitialChange?: 'when-clean' | 'never' | 'always'
|
|
50
66
|
}
|
|
51
67
|
|
|
52
68
|
export type FieldArrayOptions<I> = {
|