@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.
Files changed (66) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +64 -0
  3. package/dist/index.cjs +363 -0
  4. package/dist/index.cjs.map +1 -0
  5. package/dist/index.d.cts +178 -0
  6. package/dist/index.d.cts.map +1 -0
  7. package/dist/index.d.mts +178 -0
  8. package/dist/index.d.mts.map +1 -0
  9. package/dist/index.mjs +339 -0
  10. package/dist/index.mjs.map +1 -0
  11. package/dist/root-BImHnGj1.mjs +3270 -0
  12. package/dist/root-BImHnGj1.mjs.map +1 -0
  13. package/dist/root-Bazp5_Ik.cjs +3347 -0
  14. package/dist/root-Bazp5_Ik.cjs.map +1 -0
  15. package/dist/testing.cjs +81 -0
  16. package/dist/testing.cjs.map +1 -0
  17. package/dist/testing.d.cts +56 -0
  18. package/dist/testing.d.cts.map +1 -0
  19. package/dist/testing.d.mts +56 -0
  20. package/dist/testing.d.mts.map +1 -0
  21. package/dist/testing.mjs +78 -0
  22. package/dist/testing.mjs.map +1 -0
  23. package/dist/types-CAMgqCMz.d.mts +816 -0
  24. package/dist/types-CAMgqCMz.d.mts.map +1 -0
  25. package/dist/types-emq_lZd7.d.cts +816 -0
  26. package/dist/types-emq_lZd7.d.cts.map +1 -0
  27. package/package.json +47 -0
  28. package/src/__dev__.d.ts +8 -0
  29. package/src/controller/define.ts +50 -0
  30. package/src/controller/index.ts +12 -0
  31. package/src/controller/instance.ts +499 -0
  32. package/src/controller/root.ts +160 -0
  33. package/src/controller/types.ts +195 -0
  34. package/src/devtools.ts +0 -0
  35. package/src/emitter.ts +79 -0
  36. package/src/errors.ts +49 -0
  37. package/src/forms/field.ts +303 -0
  38. package/src/forms/form-types.ts +130 -0
  39. package/src/forms/form.ts +640 -0
  40. package/src/forms/index.ts +2 -0
  41. package/src/forms/types.ts +1 -0
  42. package/src/forms/validators.ts +70 -0
  43. package/src/index.ts +89 -0
  44. package/src/query/client.ts +934 -0
  45. package/src/query/define.ts +154 -0
  46. package/src/query/entry.ts +322 -0
  47. package/src/query/focus-online.ts +73 -0
  48. package/src/query/index.ts +3 -0
  49. package/src/query/infinite.ts +462 -0
  50. package/src/query/keys.ts +33 -0
  51. package/src/query/local.ts +113 -0
  52. package/src/query/mutation.ts +384 -0
  53. package/src/query/plugin.ts +135 -0
  54. package/src/query/types.ts +168 -0
  55. package/src/query/use.ts +321 -0
  56. package/src/scope.ts +42 -0
  57. package/src/selection.ts +146 -0
  58. package/src/signals/index.ts +3 -0
  59. package/src/signals/readonly.ts +22 -0
  60. package/src/signals/runtime.ts +115 -0
  61. package/src/signals/types.ts +31 -0
  62. package/src/testing.ts +142 -0
  63. package/src/timing/debounced.ts +32 -0
  64. package/src/timing/index.ts +2 -0
  65. package/src/timing/throttled.ts +46 -0
  66. 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,2 @@
1
+ export { debounced } from './debounced'
2
+ export { throttled } from './throttled'
@@ -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
+ }