@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/forms/form.ts
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import type { Field } from '../controller/types'
|
|
2
2
|
import { batch, computed, effect, type Signal, signal, untracked } from '../signals'
|
|
3
3
|
import type { ReadSignal } from '../signals/types'
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
bindFieldDevtoolsOwner,
|
|
6
|
+
bindFieldValidatorErrorReporter,
|
|
7
|
+
createField,
|
|
8
|
+
type ValidatorErrorReporter,
|
|
9
|
+
} from './field'
|
|
5
10
|
import type {
|
|
6
11
|
DeepPartial,
|
|
7
12
|
FieldArray,
|
|
@@ -49,21 +54,59 @@ class FormImpl<S extends FormSchema> implements Form<S> {
|
|
|
49
54
|
private readonly validators: ReadonlyArray<FormValidator<S>>
|
|
50
55
|
private readonly options: FormOptions<S> | undefined
|
|
51
56
|
private validatorDispose: (() => void) | null = null
|
|
57
|
+
private initialDispose: (() => void) | null = null
|
|
52
58
|
private currentValidatorRun = 0
|
|
53
59
|
private currentValidatorAbort: AbortController | null = null
|
|
54
60
|
private disposed = false
|
|
61
|
+
private onValidatorError: ((err: unknown) => void) | null = null
|
|
55
62
|
|
|
56
|
-
|
|
63
|
+
/** Internal — wire a sync-throw reporter for the top-level validators. */
|
|
64
|
+
bindValidatorErrorReporter(reporter: ((err: unknown) => void) | null): void {
|
|
65
|
+
this.onValidatorError = reporter
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
constructor(
|
|
69
|
+
schema: S,
|
|
70
|
+
options?: FormOptions<S>,
|
|
71
|
+
internalOptions?: { onValidatorError?: (err: unknown) => void },
|
|
72
|
+
) {
|
|
57
73
|
this.fields = schema
|
|
58
74
|
this.options = options
|
|
59
75
|
this.validators = options?.validators ?? []
|
|
60
|
-
|
|
61
|
-
//
|
|
62
|
-
|
|
63
|
-
|
|
76
|
+
// Capture reporter BEFORE the top-level validator effect kicks off in
|
|
77
|
+
// this constructor — mirrors the FieldImpl fix.
|
|
78
|
+
this.onValidatorError = internalOptions?.onValidatorError ?? null
|
|
79
|
+
|
|
80
|
+
// Initial values — supports both the static shape and the tracked-function
|
|
81
|
+
// shape from spec §8.4. For the function form, wrap in an effect so a
|
|
82
|
+
// change to any tracked signal re-seats the form (subject to the dirty
|
|
83
|
+
// guard from `resetOnInitialChange`).
|
|
64
84
|
if (options?.initial !== undefined) {
|
|
65
|
-
|
|
66
|
-
|
|
85
|
+
if (typeof options.initial === 'function') {
|
|
86
|
+
const initialFn = options.initial
|
|
87
|
+
const mode = options.resetOnInitialChange ?? 'when-clean'
|
|
88
|
+
let firstRun = true
|
|
89
|
+
this.initialDispose = effect(() => {
|
|
90
|
+
// Track signals read by `initialFn`. The dirty-guard MUST run
|
|
91
|
+
// untracked — otherwise `isDirty` would become a dep and re-seating
|
|
92
|
+
// on user input would cascade.
|
|
93
|
+
const ini = initialFn()
|
|
94
|
+
if (ini === undefined) return
|
|
95
|
+
untracked(() => {
|
|
96
|
+
if (this.disposed) return
|
|
97
|
+
if (firstRun) {
|
|
98
|
+
firstRun = false
|
|
99
|
+
this.applyPartial(ini as DeepPartial<FormValue<S>>, true)
|
|
100
|
+
return
|
|
101
|
+
}
|
|
102
|
+
if (mode === 'never') return
|
|
103
|
+
if (mode === 'when-clean' && this.isDirty.peek()) return
|
|
104
|
+
this.applyPartial(ini as DeepPartial<FormValue<S>>, true)
|
|
105
|
+
})
|
|
106
|
+
})
|
|
107
|
+
} else {
|
|
108
|
+
this.applyPartial(options.initial as DeepPartial<FormValue<S>>, true)
|
|
109
|
+
}
|
|
67
110
|
}
|
|
68
111
|
|
|
69
112
|
this.value = computed(() => this.computeValue())
|
|
@@ -145,6 +188,10 @@ class FormImpl<S extends FormSchema> implements Form<S> {
|
|
|
145
188
|
for (const [k, val] of Object.entries(partial)) {
|
|
146
189
|
const child = (this.fields as Record<string, unknown>)[k]
|
|
147
190
|
if (!child) continue
|
|
191
|
+
// `partial.someNestedForm === undefined` means "leave this subtree
|
|
192
|
+
// alone", not "reset it with undefined" — which would crash on
|
|
193
|
+
// `Object.entries(undefined)`.
|
|
194
|
+
if (val === undefined) continue
|
|
148
195
|
if (isForm(child)) {
|
|
149
196
|
// Nested form: recurse via its own `set` (user) or rebuild via reset
|
|
150
197
|
// through the same `applyPartial`-with-`asInitial` flag (initial).
|
|
@@ -155,10 +202,42 @@ class FormImpl<S extends FormSchema> implements Form<S> {
|
|
|
155
202
|
}
|
|
156
203
|
} else if (isFieldArray(child)) {
|
|
157
204
|
const arr = child
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
205
|
+
const newValues = val as unknown[]
|
|
206
|
+
if (asInitial) {
|
|
207
|
+
// Reset-style application: replace items wholesale and re-anchor
|
|
208
|
+
// them as the new initial so a later `reset()` returns here.
|
|
209
|
+
arr.clear()
|
|
210
|
+
for (const itemVal of newValues) {
|
|
211
|
+
arr.add(itemVal as ItemInitial<Field<unknown>>)
|
|
212
|
+
}
|
|
213
|
+
// Internal: re-anchor the initialItems list. `replaceInitialItems`
|
|
214
|
+
// is only exposed for this exact use case.
|
|
215
|
+
;(
|
|
216
|
+
arr as unknown as {
|
|
217
|
+
replaceInitialItems: (items: ReadonlyArray<unknown>) => void
|
|
218
|
+
}
|
|
219
|
+
).replaceInitialItems(newValues)
|
|
220
|
+
} else {
|
|
221
|
+
// User-driven patch: preserve item identity where the lengths
|
|
222
|
+
// overlap so touched / dirty / in-flight validators on existing
|
|
223
|
+
// items survive. Tail diff handles grow / shrink.
|
|
224
|
+
const current = arr.items.peek() as ReadonlyArray<Field<unknown> | Form<FormSchema>>
|
|
225
|
+
const overlap = Math.min(current.length, newValues.length)
|
|
226
|
+
for (let i = 0; i < overlap; i++) {
|
|
227
|
+
const item = current[i]
|
|
228
|
+
const v = newValues[i]
|
|
229
|
+
if (isForm(item)) {
|
|
230
|
+
item.set(v as DeepPartial<FormValue<FormSchema>>)
|
|
231
|
+
} else {
|
|
232
|
+
;(item as Field<unknown>).set(v)
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
for (let i = current.length; i < newValues.length; i++) {
|
|
236
|
+
arr.add(newValues[i] as ItemInitial<Field<unknown>>)
|
|
237
|
+
}
|
|
238
|
+
for (let i = current.length - 1; i >= newValues.length; i--) {
|
|
239
|
+
arr.remove(i)
|
|
240
|
+
}
|
|
162
241
|
}
|
|
163
242
|
} else {
|
|
164
243
|
const f = child as Field<unknown>
|
|
@@ -214,6 +293,12 @@ class FormImpl<S extends FormSchema> implements Form<S> {
|
|
|
214
293
|
}
|
|
215
294
|
}
|
|
216
295
|
await Promise.all(tasks)
|
|
296
|
+
// Kick a fresh top-level run so the surface matches "re-run every
|
|
297
|
+
// validator" — without this, `validate()` would skip top-level if it
|
|
298
|
+
// settled before the call and the value hasn't tracked-changed since.
|
|
299
|
+
if (this.validators.length > 0) {
|
|
300
|
+
this.runTopLevelValidators()
|
|
301
|
+
}
|
|
217
302
|
// Wait for top-level validators to finish.
|
|
218
303
|
if (this.topLevelValidating$.peek()) {
|
|
219
304
|
await new Promise<void>((resolve) => {
|
|
@@ -232,6 +317,7 @@ class FormImpl<S extends FormSchema> implements Form<S> {
|
|
|
232
317
|
if (this.disposed) return
|
|
233
318
|
this.disposed = true
|
|
234
319
|
this.validatorDispose?.()
|
|
320
|
+
this.initialDispose?.()
|
|
235
321
|
this.currentValidatorAbort?.abort()
|
|
236
322
|
for (const child of Object.values(this.fields)) {
|
|
237
323
|
;(child as { dispose?: () => void }).dispose?.()
|
|
@@ -249,9 +335,18 @@ class FormImpl<S extends FormSchema> implements Form<S> {
|
|
|
249
335
|
const syncErrors: string[] = []
|
|
250
336
|
const asyncPromises: Promise<string | null>[] = []
|
|
251
337
|
for (const v of this.validators) {
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
338
|
+
try {
|
|
339
|
+
const r = v(value, abort.signal)
|
|
340
|
+
if (r instanceof Promise) asyncPromises.push(r)
|
|
341
|
+
else if (r != null) syncErrors.push(r)
|
|
342
|
+
} catch (err) {
|
|
343
|
+
try {
|
|
344
|
+
this.onValidatorError?.(err)
|
|
345
|
+
} catch {
|
|
346
|
+
// The reporter must not propagate.
|
|
347
|
+
}
|
|
348
|
+
syncErrors.push(err instanceof Error ? err.message : String(err))
|
|
349
|
+
}
|
|
255
350
|
}
|
|
256
351
|
|
|
257
352
|
if (syncErrors.length > 0) {
|
|
@@ -340,16 +435,27 @@ class FieldArrayImpl<I extends Field<any> | Form<any>> implements FieldArray<I>
|
|
|
340
435
|
private readonly topLevelValidating$: Signal<boolean> = signal(false)
|
|
341
436
|
|
|
342
437
|
private readonly itemFactory: (initial?: ItemInitial<I>) => I
|
|
343
|
-
private
|
|
438
|
+
private initialItems: Array<ItemInitial<I>> = []
|
|
344
439
|
private readonly validators: ReadonlyArray<FieldArrayValidator<I>>
|
|
345
440
|
private currentValidatorRun = 0
|
|
346
441
|
private currentValidatorAbort: AbortController | null = null
|
|
347
442
|
private validatorDispose: (() => void) | null = null
|
|
348
443
|
private disposed = false
|
|
444
|
+
private onValidatorError: ((err: unknown) => void) | null = null
|
|
349
445
|
|
|
350
|
-
|
|
446
|
+
/** Internal — see `FormImpl.bindValidatorErrorReporter`. */
|
|
447
|
+
bindValidatorErrorReporter(reporter: ((err: unknown) => void) | null): void {
|
|
448
|
+
this.onValidatorError = reporter
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
constructor(
|
|
452
|
+
itemFactory: (initial?: ItemInitial<I>) => I,
|
|
453
|
+
options?: FieldArrayOptions<I>,
|
|
454
|
+
internalOptions?: { onValidatorError?: (err: unknown) => void },
|
|
455
|
+
) {
|
|
351
456
|
this.itemFactory = itemFactory
|
|
352
457
|
this.validators = options?.validators ?? []
|
|
458
|
+
this.onValidatorError = internalOptions?.onValidatorError ?? null
|
|
353
459
|
this.items$ = signal<I[]>([])
|
|
354
460
|
if (options?.initial) {
|
|
355
461
|
this.initialItems = options.initial
|
|
@@ -454,6 +560,16 @@ class FieldArrayImpl<I extends Field<any> | Form<any>> implements FieldArray<I>
|
|
|
454
560
|
this.items$.set([])
|
|
455
561
|
}
|
|
456
562
|
|
|
563
|
+
/**
|
|
564
|
+
* Internal — used by `Form.resetWithInitial` to re-anchor the array's
|
|
565
|
+
* initial items after a parent-driven `applyPartial(..., asInitial: true)`.
|
|
566
|
+
* Without this, a subsequent `reset()` would revert to the construction-
|
|
567
|
+
* time initials rather than the most-recently-applied ones.
|
|
568
|
+
*/
|
|
569
|
+
replaceInitialItems(items: ReadonlyArray<ItemInitial<I>>): void {
|
|
570
|
+
this.initialItems = [...items]
|
|
571
|
+
}
|
|
572
|
+
|
|
457
573
|
reset(): void {
|
|
458
574
|
if (this.disposed) return
|
|
459
575
|
batch(() => {
|
|
@@ -480,6 +596,10 @@ class FieldArrayImpl<I extends Field<any> | Form<any>> implements FieldArray<I>
|
|
|
480
596
|
else tasks.push((item as Field<unknown>).revalidate())
|
|
481
597
|
}
|
|
482
598
|
await Promise.all(tasks)
|
|
599
|
+
// Fresh top-level run — see `FormImpl.validate` for the rationale.
|
|
600
|
+
if (this.validators.length > 0) {
|
|
601
|
+
this.runTopLevelValidators()
|
|
602
|
+
}
|
|
483
603
|
if (this.topLevelValidating$.peek()) {
|
|
484
604
|
await new Promise<void>((resolve) => {
|
|
485
605
|
const unsub = this.topLevelValidating$.subscribe((v) => {
|
|
@@ -514,9 +634,18 @@ class FieldArrayImpl<I extends Field<any> | Form<any>> implements FieldArray<I>
|
|
|
514
634
|
const syncErrors: string[] = []
|
|
515
635
|
const asyncPromises: Promise<string | null>[] = []
|
|
516
636
|
for (const v of this.validators) {
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
637
|
+
try {
|
|
638
|
+
const r = v(value, abort.signal)
|
|
639
|
+
if (r instanceof Promise) asyncPromises.push(r)
|
|
640
|
+
else if (r != null) syncErrors.push(r)
|
|
641
|
+
} catch (err) {
|
|
642
|
+
try {
|
|
643
|
+
this.onValidatorError?.(err)
|
|
644
|
+
} catch {
|
|
645
|
+
// The reporter must not propagate.
|
|
646
|
+
}
|
|
647
|
+
syncErrors.push(err instanceof Error ? err.message : String(err))
|
|
648
|
+
}
|
|
520
649
|
}
|
|
521
650
|
|
|
522
651
|
if (syncErrors.length > 0) {
|
|
@@ -554,15 +683,20 @@ class FieldArrayImpl<I extends Field<any> | Form<any>> implements FieldArray<I>
|
|
|
554
683
|
}
|
|
555
684
|
}
|
|
556
685
|
|
|
557
|
-
export function createForm<S extends FormSchema>(
|
|
558
|
-
|
|
686
|
+
export function createForm<S extends FormSchema>(
|
|
687
|
+
schema: S,
|
|
688
|
+
options?: FormOptions<S>,
|
|
689
|
+
internalOptions?: { onValidatorError?: (err: unknown) => void },
|
|
690
|
+
): Form<S> {
|
|
691
|
+
return new FormImpl(schema, options, internalOptions)
|
|
559
692
|
}
|
|
560
693
|
|
|
561
694
|
export function createFieldArray<I extends Field<any> | Form<any>>(
|
|
562
695
|
itemFactory: (initial?: ItemInitial<I>) => I,
|
|
563
696
|
options?: FieldArrayOptions<I>,
|
|
697
|
+
internalOptions?: { onValidatorError?: (err: unknown) => void },
|
|
564
698
|
): FieldArray<I> {
|
|
565
|
-
return new FieldArrayImpl<I>(itemFactory, options)
|
|
699
|
+
return new FieldArrayImpl<I>(itemFactory, options, internalOptions)
|
|
566
700
|
}
|
|
567
701
|
|
|
568
702
|
/**
|
|
@@ -614,16 +748,40 @@ function bindTreeToDevtoolsInto(
|
|
|
614
748
|
}
|
|
615
749
|
if (isFieldArray(node)) {
|
|
616
750
|
// Re-bind on every items change so dynamically-added entries get tracked.
|
|
617
|
-
//
|
|
618
|
-
//
|
|
751
|
+
// Each re-bind has its own disposer set scoped to that pass; on the next
|
|
752
|
+
// items change we flush the previous pass's disposers BEFORE creating the
|
|
753
|
+
// new effects, so a churning array doesn't accumulate reactive work.
|
|
754
|
+
// (Pre-fix, every items mutation appended fresh effects to the outer
|
|
755
|
+
// `disposers` array and never released the old ones.)
|
|
619
756
|
const arr = node as FieldArray<Field<unknown> | Form<FormSchema>>
|
|
757
|
+
let perPass: Array<() => void> = []
|
|
620
758
|
const stop = effect(() => {
|
|
621
759
|
const items = arr.items.value
|
|
760
|
+
// Flush previous pass before rebinding the new item set.
|
|
761
|
+
for (const d of perPass) {
|
|
762
|
+
try {
|
|
763
|
+
d()
|
|
764
|
+
} catch {
|
|
765
|
+
// Disposer failures must not break sibling cleanup.
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
perPass = []
|
|
622
769
|
items.forEach((item, idx) => {
|
|
623
|
-
bindTreeToDevtoolsInto(item, `${prefix}[${idx}]`, controllerPath, emitter,
|
|
770
|
+
bindTreeToDevtoolsInto(item, `${prefix}[${idx}]`, controllerPath, emitter, perPass)
|
|
624
771
|
})
|
|
625
772
|
})
|
|
626
773
|
disposers.push(stop)
|
|
774
|
+
// On final dispose, drain the per-pass disposers too.
|
|
775
|
+
disposers.push(() => {
|
|
776
|
+
for (const d of perPass) {
|
|
777
|
+
try {
|
|
778
|
+
d()
|
|
779
|
+
} catch {
|
|
780
|
+
// Ignore.
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
perPass = []
|
|
784
|
+
})
|
|
627
785
|
return
|
|
628
786
|
}
|
|
629
787
|
// Leaf Field.
|
|
@@ -634,6 +792,40 @@ function bindTreeToDevtoolsInto(
|
|
|
634
792
|
})
|
|
635
793
|
}
|
|
636
794
|
|
|
795
|
+
/**
|
|
796
|
+
* Walk a Form/FieldArray subtree and install `reporter` on every level —
|
|
797
|
+
* leaf fields, nested forms' top-level validators, and field-arrays' top-level
|
|
798
|
+
* validators. Called by `ctx.form` / `ctx.fieldArray` so synchronous validator
|
|
799
|
+
* throws anywhere in the tree route through `root.onError`. See
|
|
800
|
+
* `ValidatorErrorReporter` in `./field.ts`.
|
|
801
|
+
*/
|
|
802
|
+
export function bindTreeValidatorErrorReporter(
|
|
803
|
+
node: Field<unknown> | Form<FormSchema> | FieldArray<Field<unknown> | Form<FormSchema>>,
|
|
804
|
+
reporter: ValidatorErrorReporter | null,
|
|
805
|
+
): void {
|
|
806
|
+
if (isForm(node)) {
|
|
807
|
+
const impl = node as { bindValidatorErrorReporter?: (r: ValidatorErrorReporter | null) => void }
|
|
808
|
+
impl.bindValidatorErrorReporter?.(reporter)
|
|
809
|
+
for (const child of Object.values(node.fields)) {
|
|
810
|
+
bindTreeValidatorErrorReporter(child, reporter)
|
|
811
|
+
}
|
|
812
|
+
return
|
|
813
|
+
}
|
|
814
|
+
if (isFieldArray(node)) {
|
|
815
|
+
const impl = node as { bindValidatorErrorReporter?: (r: ValidatorErrorReporter | null) => void }
|
|
816
|
+
impl.bindValidatorErrorReporter?.(reporter)
|
|
817
|
+
// Items currently in the array. (Items added later won't get the reporter
|
|
818
|
+
// unless `ctx.fieldArray` is wrapped to rebind — but the leaf items in the
|
|
819
|
+
// typical pattern come from a user factory that constructs through
|
|
820
|
+
// `createField` and is bound here by the parent traversal.)
|
|
821
|
+
for (const item of node.items.value) {
|
|
822
|
+
bindTreeValidatorErrorReporter(item, reporter)
|
|
823
|
+
}
|
|
824
|
+
return
|
|
825
|
+
}
|
|
826
|
+
bindFieldValidatorErrorReporter(node as Field<unknown>, reporter)
|
|
827
|
+
}
|
|
828
|
+
|
|
637
829
|
// Quiet unused-import linter without exporting these symbols publicly.
|
|
638
830
|
void createField
|
|
639
831
|
void untracked
|
package/src/index.ts
CHANGED
|
@@ -3,11 +3,17 @@
|
|
|
3
3
|
// Controller container
|
|
4
4
|
export type {
|
|
5
5
|
AmbientDeps,
|
|
6
|
+
Collection,
|
|
7
|
+
CollectionFactoryApi,
|
|
8
|
+
CollectionFactoryOptions,
|
|
9
|
+
CollectionFactoryResult,
|
|
10
|
+
CollectionHomogeneousOptions,
|
|
6
11
|
ControllerDef,
|
|
7
12
|
CtrlApi,
|
|
8
13
|
CtrlProps,
|
|
9
14
|
Ctx,
|
|
10
15
|
Field,
|
|
16
|
+
LazyChild,
|
|
11
17
|
Root,
|
|
12
18
|
RootOptions,
|
|
13
19
|
} from './controller'
|
|
@@ -16,7 +22,7 @@ export { createRoot, defineController } from './controller'
|
|
|
16
22
|
export type { DebugBus, DebugCacheEntry, DebugEvent } from './devtools'
|
|
17
23
|
|
|
18
24
|
// Emitter
|
|
19
|
-
export type { Emitter } from './emitter'
|
|
25
|
+
export type { Emitter, EmitterErrorReporter } from './emitter'
|
|
20
26
|
export { createEmitter } from './emitter'
|
|
21
27
|
export type { ErrorContext } from './errors'
|
|
22
28
|
// Forms — stdlib validators + debouncedValidator
|
|
@@ -44,6 +50,11 @@ export type {
|
|
|
44
50
|
InfiniteQuerySpec,
|
|
45
51
|
InfiniteQuerySubscription,
|
|
46
52
|
} from './query/infinite'
|
|
53
|
+
// Key hashing — exported so plugins (entities, etc.) that need a stable
|
|
54
|
+
// per-`keyArgs` index key reuse the canonical implementation instead of
|
|
55
|
+
// rolling their own ad-hoc JSON.stringify (which mishandles Date, key
|
|
56
|
+
// ordering, and `undefined`).
|
|
57
|
+
export { stableHash } from './query/keys'
|
|
47
58
|
export type {
|
|
48
59
|
Mutation,
|
|
49
60
|
MutationConcurrency,
|