@kontsedal/olas-core 0.0.1-rc.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/LICENSE +21 -0
- package/README.md +64 -0
- package/dist/index.cjs +363 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +178 -0
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.mts +178 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +339 -0
- package/dist/index.mjs.map +1 -0
- package/dist/root-BImHnGj1.mjs +3270 -0
- package/dist/root-BImHnGj1.mjs.map +1 -0
- package/dist/root-Bazp5_Ik.cjs +3347 -0
- package/dist/root-Bazp5_Ik.cjs.map +1 -0
- package/dist/testing.cjs +81 -0
- package/dist/testing.cjs.map +1 -0
- package/dist/testing.d.cts +56 -0
- package/dist/testing.d.cts.map +1 -0
- package/dist/testing.d.mts +56 -0
- package/dist/testing.d.mts.map +1 -0
- package/dist/testing.mjs +78 -0
- package/dist/testing.mjs.map +1 -0
- package/dist/types-CAMgqCMz.d.mts +816 -0
- package/dist/types-CAMgqCMz.d.mts.map +1 -0
- package/dist/types-emq_lZd7.d.cts +816 -0
- package/dist/types-emq_lZd7.d.cts.map +1 -0
- package/package.json +47 -0
- package/src/__dev__.d.ts +8 -0
- package/src/controller/define.ts +50 -0
- package/src/controller/index.ts +12 -0
- package/src/controller/instance.ts +499 -0
- package/src/controller/root.ts +160 -0
- package/src/controller/types.ts +195 -0
- package/src/devtools.ts +0 -0
- package/src/emitter.ts +79 -0
- package/src/errors.ts +49 -0
- package/src/forms/field.ts +303 -0
- package/src/forms/form-types.ts +130 -0
- package/src/forms/form.ts +640 -0
- package/src/forms/index.ts +2 -0
- package/src/forms/types.ts +1 -0
- package/src/forms/validators.ts +70 -0
- package/src/index.ts +89 -0
- package/src/query/client.ts +934 -0
- package/src/query/define.ts +154 -0
- package/src/query/entry.ts +322 -0
- package/src/query/focus-online.ts +73 -0
- package/src/query/index.ts +3 -0
- package/src/query/infinite.ts +462 -0
- package/src/query/keys.ts +33 -0
- package/src/query/local.ts +113 -0
- package/src/query/mutation.ts +384 -0
- package/src/query/plugin.ts +135 -0
- package/src/query/types.ts +168 -0
- package/src/query/use.ts +321 -0
- package/src/scope.ts +42 -0
- package/src/selection.ts +146 -0
- package/src/signals/index.ts +3 -0
- package/src/signals/readonly.ts +22 -0
- package/src/signals/runtime.ts +115 -0
- package/src/signals/types.ts +31 -0
- package/src/testing.ts +142 -0
- package/src/timing/debounced.ts +32 -0
- package/src/timing/index.ts +2 -0
- package/src/timing/throttled.ts +46 -0
- package/src/utils.ts +13 -0
package/src/testing.ts
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { createRootWithProps } from './controller/root'
|
|
2
|
+
import type { ControllerDef, Field, Root, RootOptions } from './controller/types'
|
|
3
|
+
import type { AsyncState, AsyncStatus } from './query/types'
|
|
4
|
+
import { computed, type ReadSignal, type Signal, signal } from './signals'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Construct an isolated root wrapping a single controller. The returned object
|
|
8
|
+
* is the controller's api plus the standard Root lifecycle controls
|
|
9
|
+
* (`dispose`, `suspend`, `resume`, `__debug`).
|
|
10
|
+
*
|
|
11
|
+
* Equivalent to defining a tiny root wrapper, but ergonomic in tests.
|
|
12
|
+
*/
|
|
13
|
+
export function createTestController<
|
|
14
|
+
Props,
|
|
15
|
+
Api,
|
|
16
|
+
TDeps extends Record<string, unknown> = Record<string, unknown>,
|
|
17
|
+
>(
|
|
18
|
+
def: ControllerDef<Props, Api>,
|
|
19
|
+
options: {
|
|
20
|
+
deps: TDeps
|
|
21
|
+
props: Props
|
|
22
|
+
onError?: RootOptions<TDeps>['onError']
|
|
23
|
+
},
|
|
24
|
+
): Root<Api> {
|
|
25
|
+
return createRootWithProps<Props, Api, TDeps>(def, options.props, {
|
|
26
|
+
deps: options.deps,
|
|
27
|
+
onError: options.onError,
|
|
28
|
+
})
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Shape-correct fake `Field<T>` for UI tests. Pass an initial value plus any
|
|
33
|
+
* overrides for the read-only signals. The returned object satisfies `Field<T>`
|
|
34
|
+
* so it can be passed straight into `useField(...)` or any component that
|
|
35
|
+
* accepts a real field. See spec §20.10.
|
|
36
|
+
*/
|
|
37
|
+
export function fakeField<T>(
|
|
38
|
+
initial: T,
|
|
39
|
+
overrides?: Partial<{
|
|
40
|
+
errors: string[]
|
|
41
|
+
isValid: boolean
|
|
42
|
+
isDirty: boolean
|
|
43
|
+
touched: boolean
|
|
44
|
+
isValidating: boolean
|
|
45
|
+
set: (value: T) => void
|
|
46
|
+
setAsInitial: (value: T) => void
|
|
47
|
+
reset: () => void
|
|
48
|
+
markTouched: () => void
|
|
49
|
+
revalidate: () => Promise<boolean>
|
|
50
|
+
dispose: () => void
|
|
51
|
+
}>,
|
|
52
|
+
): Field<T> {
|
|
53
|
+
const value$: Signal<T> = signal(initial)
|
|
54
|
+
const errors$: Signal<string[]> = signal(overrides?.errors ?? [])
|
|
55
|
+
const touched$: Signal<boolean> = signal(overrides?.touched ?? false)
|
|
56
|
+
const dirty$: Signal<boolean> = signal(overrides?.isDirty ?? false)
|
|
57
|
+
const validating$: Signal<boolean> = signal(overrides?.isValidating ?? false)
|
|
58
|
+
const isValid$: ReadSignal<boolean> =
|
|
59
|
+
overrides?.isValid !== undefined
|
|
60
|
+
? signal(overrides.isValid)
|
|
61
|
+
: computed(() => errors$.value.length === 0 && !validating$.value)
|
|
62
|
+
|
|
63
|
+
let currentInitial = initial
|
|
64
|
+
const set = overrides?.set ?? ((next: T) => value$.set(next))
|
|
65
|
+
const setAsInitial =
|
|
66
|
+
overrides?.setAsInitial ??
|
|
67
|
+
((next: T) => {
|
|
68
|
+
currentInitial = next
|
|
69
|
+
value$.set(next)
|
|
70
|
+
dirty$.set(false)
|
|
71
|
+
})
|
|
72
|
+
const fake: Field<T> = {
|
|
73
|
+
get value() {
|
|
74
|
+
return value$.value
|
|
75
|
+
},
|
|
76
|
+
peek: () => value$.peek(),
|
|
77
|
+
subscribe: (handler) => value$.subscribe(handler),
|
|
78
|
+
errors: errors$,
|
|
79
|
+
isValid: isValid$,
|
|
80
|
+
isDirty: dirty$,
|
|
81
|
+
touched: touched$,
|
|
82
|
+
isValidating: validating$,
|
|
83
|
+
set,
|
|
84
|
+
setAsInitial,
|
|
85
|
+
reset: overrides?.reset ?? (() => value$.set(currentInitial)),
|
|
86
|
+
markTouched: overrides?.markTouched ?? (() => touched$.set(true)),
|
|
87
|
+
revalidate: overrides?.revalidate ?? (async () => errors$.peek().length === 0),
|
|
88
|
+
dispose: overrides?.dispose ?? (() => {}),
|
|
89
|
+
}
|
|
90
|
+
return fake
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Shape-correct fake `AsyncState<T>` for UI tests. Pass overrides for any of
|
|
95
|
+
* the signal-backed properties; everything else falls back to inert defaults.
|
|
96
|
+
* The returned object satisfies `AsyncState<T>` so it can stand in for a real
|
|
97
|
+
* query subscription in component tests. See spec §20.10.
|
|
98
|
+
*/
|
|
99
|
+
export function fakeAsyncState<T>(
|
|
100
|
+
overrides?: Partial<{
|
|
101
|
+
data: T | undefined
|
|
102
|
+
error: unknown | undefined
|
|
103
|
+
status: AsyncStatus
|
|
104
|
+
isLoading: boolean
|
|
105
|
+
isFetching: boolean
|
|
106
|
+
isStale: boolean
|
|
107
|
+
lastUpdatedAt: number | undefined
|
|
108
|
+
hasPendingMutations: boolean
|
|
109
|
+
refetch: () => Promise<T>
|
|
110
|
+
reset: () => void
|
|
111
|
+
firstValue: () => Promise<T>
|
|
112
|
+
}>,
|
|
113
|
+
): AsyncState<T> {
|
|
114
|
+
const data$: ReadSignal<T | undefined> = signal(overrides?.data)
|
|
115
|
+
const error$: ReadSignal<unknown | undefined> = signal(overrides?.error)
|
|
116
|
+
const status$: ReadSignal<AsyncStatus> = signal(
|
|
117
|
+
overrides?.status ?? (overrides?.data !== undefined ? 'success' : 'idle'),
|
|
118
|
+
)
|
|
119
|
+
const isLoading$: ReadSignal<boolean> = signal(overrides?.isLoading ?? false)
|
|
120
|
+
const isFetching$: ReadSignal<boolean> = signal(overrides?.isFetching ?? false)
|
|
121
|
+
const isStale$: ReadSignal<boolean> = signal(overrides?.isStale ?? false)
|
|
122
|
+
const lastUpdatedAt$: ReadSignal<number | undefined> = signal(overrides?.lastUpdatedAt)
|
|
123
|
+
const hasPendingMutations$: ReadSignal<boolean> = signal(overrides?.hasPendingMutations ?? false)
|
|
124
|
+
|
|
125
|
+
const refetch = overrides?.refetch ?? (async () => data$.peek() as T)
|
|
126
|
+
const reset = overrides?.reset ?? (() => {})
|
|
127
|
+
const firstValue = overrides?.firstValue ?? (async () => data$.peek() as T)
|
|
128
|
+
|
|
129
|
+
return {
|
|
130
|
+
data: data$,
|
|
131
|
+
error: error$,
|
|
132
|
+
status: status$,
|
|
133
|
+
isLoading: isLoading$,
|
|
134
|
+
isFetching: isFetching$,
|
|
135
|
+
isStale: isStale$,
|
|
136
|
+
lastUpdatedAt: lastUpdatedAt$,
|
|
137
|
+
hasPendingMutations: hasPendingMutations$,
|
|
138
|
+
refetch,
|
|
139
|
+
reset,
|
|
140
|
+
firstValue,
|
|
141
|
+
}
|
|
142
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { effect, signal } from '../signals'
|
|
2
|
+
import type { ReadSignal } from '../signals/types'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Lag a signal by `ms`. The returned signal updates only after the source has
|
|
6
|
+
* been unchanged for `ms`. Each new write resets the timer.
|
|
7
|
+
*
|
|
8
|
+
* No lifecycle — the internal effect runs for the lifetime of the program.
|
|
9
|
+
* Use inside a controller closure so it dies with the closure.
|
|
10
|
+
*/
|
|
11
|
+
export function debounced<T>(source: ReadSignal<T>, ms: number): ReadSignal<T> {
|
|
12
|
+
const out = signal<T>(source.peek())
|
|
13
|
+
let timer: ReturnType<typeof setTimeout> | null = null
|
|
14
|
+
let initial = true
|
|
15
|
+
|
|
16
|
+
effect(() => {
|
|
17
|
+
const value = source.value
|
|
18
|
+
if (initial) {
|
|
19
|
+
// The first effect run reads the source for tracking; we already
|
|
20
|
+
// initialized `out` to the same value, so skip scheduling.
|
|
21
|
+
initial = false
|
|
22
|
+
return
|
|
23
|
+
}
|
|
24
|
+
if (timer != null) clearTimeout(timer)
|
|
25
|
+
timer = setTimeout(() => {
|
|
26
|
+
out.set(value)
|
|
27
|
+
timer = null
|
|
28
|
+
}, ms)
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
return out
|
|
32
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { effect, signal } from '../signals'
|
|
2
|
+
import type { ReadSignal } from '../signals/types'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Rate-limit a signal so it emits at most once per `ms` (leading + trailing).
|
|
6
|
+
* The first change passes through immediately. Subsequent changes within the
|
|
7
|
+
* window are coalesced; the latest value is emitted when the window expires.
|
|
8
|
+
*
|
|
9
|
+
* No lifecycle — see debounced() note.
|
|
10
|
+
*/
|
|
11
|
+
export function throttled<T>(source: ReadSignal<T>, ms: number): ReadSignal<T> {
|
|
12
|
+
const out = signal<T>(source.peek())
|
|
13
|
+
let lastEmit = Number.NEGATIVE_INFINITY
|
|
14
|
+
let trailingTimer: ReturnType<typeof setTimeout> | null = null
|
|
15
|
+
let trailingValue: T = source.peek()
|
|
16
|
+
let initial = true
|
|
17
|
+
|
|
18
|
+
effect(() => {
|
|
19
|
+
const value = source.value
|
|
20
|
+
if (initial) {
|
|
21
|
+
initial = false
|
|
22
|
+
return
|
|
23
|
+
}
|
|
24
|
+
const now = Date.now()
|
|
25
|
+
const elapsed = now - lastEmit
|
|
26
|
+
if (elapsed >= ms) {
|
|
27
|
+
out.set(value)
|
|
28
|
+
lastEmit = now
|
|
29
|
+
if (trailingTimer != null) {
|
|
30
|
+
clearTimeout(trailingTimer)
|
|
31
|
+
trailingTimer = null
|
|
32
|
+
}
|
|
33
|
+
} else {
|
|
34
|
+
trailingValue = value
|
|
35
|
+
if (trailingTimer == null) {
|
|
36
|
+
trailingTimer = setTimeout(() => {
|
|
37
|
+
out.set(trailingValue)
|
|
38
|
+
lastEmit = Date.now()
|
|
39
|
+
trailingTimer = null
|
|
40
|
+
}, ms - elapsed)
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
return out
|
|
46
|
+
}
|
package/src/utils.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* True iff `err` is an AbortError. Used to filter superseded latest-wins
|
|
3
|
+
* mutations and aborted fetches from genuine failures.
|
|
4
|
+
*
|
|
5
|
+
* Spec: §20.12 — checks `err instanceof DOMException && err.name === 'AbortError'`.
|
|
6
|
+
* Node 17+ exposes a global DOMException, so this works server-side too.
|
|
7
|
+
*/
|
|
8
|
+
export function isAbortError(err: unknown): boolean {
|
|
9
|
+
if (typeof DOMException !== 'undefined' && err instanceof DOMException) {
|
|
10
|
+
return err.name === 'AbortError'
|
|
11
|
+
}
|
|
12
|
+
return false
|
|
13
|
+
}
|