@kontsedal/olas-core 0.0.2 → 0.0.4

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 (44) hide show
  1. package/dist/index.cjs +34 -1
  2. package/dist/index.cjs.map +1 -1
  3. package/dist/index.d.cts +52 -2
  4. package/dist/index.d.cts.map +1 -1
  5. package/dist/index.d.mts +52 -2
  6. package/dist/index.d.mts.map +1 -1
  7. package/dist/index.mjs +33 -2
  8. package/dist/index.mjs.map +1 -1
  9. package/dist/{root-De-6KWIZ.mjs → root-BBSlzvJ2.mjs} +251 -18
  10. package/dist/root-BBSlzvJ2.mjs.map +1 -0
  11. package/dist/{root-XKEsSmcd.cjs → root-CoafhkTg.cjs} +251 -18
  12. package/dist/root-CoafhkTg.cjs.map +1 -0
  13. package/dist/testing.cjs +23 -11
  14. package/dist/testing.cjs.map +1 -1
  15. package/dist/testing.d.cts +3 -1
  16. package/dist/testing.d.cts.map +1 -1
  17. package/dist/testing.d.mts +3 -1
  18. package/dist/testing.d.mts.map +1 -1
  19. package/dist/testing.mjs +23 -11
  20. package/dist/testing.mjs.map +1 -1
  21. package/dist/{types-C-zV1JZA.d.mts → types-BCf2nB2N.d.mts} +73 -8
  22. package/dist/types-BCf2nB2N.d.mts.map +1 -0
  23. package/dist/{types-DKfpkm17.d.cts → types-Ijeun3qo.d.cts} +73 -8
  24. package/dist/types-Ijeun3qo.d.cts.map +1 -0
  25. package/package.json +1 -1
  26. package/src/controller/types.ts +26 -2
  27. package/src/forms/field.ts +42 -9
  28. package/src/forms/form-types.ts +37 -0
  29. package/src/forms/form.ts +118 -0
  30. package/src/forms/index.ts +12 -1
  31. package/src/forms/standard-schema.ts +37 -0
  32. package/src/forms/validators.ts +31 -0
  33. package/src/index.ts +13 -3
  34. package/src/query/entry.ts +10 -3
  35. package/src/query/infinite.ts +8 -1
  36. package/src/query/local.ts +1 -0
  37. package/src/query/structural-share.ts +114 -0
  38. package/src/query/types.ts +22 -2
  39. package/src/query/use.ts +53 -13
  40. package/src/testing.ts +5 -0
  41. package/dist/root-De-6KWIZ.mjs.map +0 -1
  42. package/dist/root-XKEsSmcd.cjs.map +0 -1
  43. package/dist/types-C-zV1JZA.d.mts.map +0 -1
  44. package/dist/types-DKfpkm17.d.cts.map +0 -1
package/src/query/use.ts CHANGED
@@ -2,17 +2,24 @@ import { computed, effect, type Signal, signal, untracked } from '../signals'
2
2
  import type { ReadSignal } from '../signals/types'
3
3
  import type { ClientEntry, InfiniteClientEntry, QueryClient } from './client'
4
4
  import type { InfiniteQuery, InfiniteQuerySpec, InfiniteQuerySubscription } from './infinite'
5
- import type { AsyncStatus, Query, QuerySpec, QuerySubscription, UseOptions } from './types'
5
+ import type {
6
+ AsyncStatus,
7
+ Query,
8
+ QuerySpec,
9
+ QuerySubscription,
10
+ UseInternalOptions,
11
+ UseOptions,
12
+ } from './types'
6
13
 
7
14
  type QueryInternal<Args extends unknown[], T> = Query<Args, T> & {
8
15
  readonly __spec: QuerySpec<Args, T>
9
16
  }
10
17
 
11
- class SubscriptionImpl<T> implements QuerySubscription<T> {
18
+ class SubscriptionImpl<T, U = T> implements QuerySubscription<U> {
12
19
  private readonly current$: Signal<ClientEntry<T> | null> = signal(null)
13
20
  private readonly previousData$: Signal<T | undefined> = signal(undefined)
14
21
 
15
- readonly data: ReadSignal<T | undefined>
22
+ readonly data: ReadSignal<U | undefined>
16
23
  readonly error: ReadSignal<unknown | undefined>
17
24
  readonly status: ReadSignal<AsyncStatus>
18
25
  readonly isLoading: ReadSignal<boolean>
@@ -21,14 +28,30 @@ class SubscriptionImpl<T> implements QuerySubscription<T> {
21
28
  readonly lastUpdatedAt: ReadSignal<number | undefined>
22
29
  readonly hasPendingMutations: ReadSignal<boolean>
23
30
 
24
- constructor(private readonly keepPreviousData: boolean) {
25
- this.data = computed(() => {
31
+ constructor(
32
+ private readonly keepPreviousData: boolean,
33
+ private readonly select?: (data: T) => U,
34
+ ) {
35
+ // The underlying entry stores `T`. The subscription's `data` is `U`
36
+ // (or `T` when no projection). We compute the raw `T` once, then layer
37
+ // `select` in a second computed so the projection's `Object.is` dedup
38
+ // applies BEFORE downstream subscribers run — combined with structural
39
+ // sharing on the entry, an unchanged payload + a stable `select`
40
+ // outputs the same `U` reference and doesn't churn the React tree.
41
+ const rawData = computed(() => {
26
42
  const cur = this.current$.value
27
43
  const curData = cur?.entry.data.value
28
44
  if (curData !== undefined) return curData
29
45
  if (keepPreviousData) return this.previousData$.value
30
46
  return undefined
31
47
  })
48
+ this.data =
49
+ select === undefined
50
+ ? (rawData as unknown as ReadSignal<U | undefined>)
51
+ : computed<U | undefined>(() => {
52
+ const raw = rawData.value
53
+ return raw === undefined ? undefined : select(raw)
54
+ })
32
55
  this.error = computed(() => this.current$.value?.entry.error.value)
33
56
  this.status = computed<AsyncStatus>(() => this.current$.value?.entry.status.value ?? 'idle')
34
57
  this.isLoading = computed(() => {
@@ -59,33 +82,45 @@ class SubscriptionImpl<T> implements QuerySubscription<T> {
59
82
  this.current$.set(null)
60
83
  }
61
84
 
62
- refetch = (): Promise<T> => {
85
+ refetch = (): Promise<U> => {
63
86
  const cur = this.current$.peek()
64
87
  if (!cur) return Promise.reject(new Error('[olas] no active subscription'))
65
- return cur.entry.refetch()
88
+ return cur.entry.refetch().then((v) => this.project(v))
66
89
  }
67
90
 
68
91
  reset = (): void => {
69
92
  this.current$.peek()?.entry.reset()
70
93
  }
71
94
 
72
- firstValue = (): Promise<T> => {
95
+ firstValue = (): Promise<U> => {
73
96
  const cur = this.current$.peek()
74
97
  if (!cur) return Promise.reject(new Error('[olas] no active subscription'))
75
- return cur.entry.firstValue()
98
+ return cur.entry.firstValue().then((v) => this.project(v))
99
+ }
100
+
101
+ // Alias surfaced on `AsyncState` for Suspense / React 19 `use(...)`.
102
+ promise = (): Promise<U> => this.firstValue()
103
+
104
+ private project(v: T): U {
105
+ return this.select === undefined ? (v as unknown as U) : this.select(v)
76
106
  }
77
107
  }
78
108
 
79
109
  /**
80
110
  * Build a subscription + the effect that keeps it bound to the right entry.
81
111
  * The controller container wires the disposer into the lifecycle.
112
+ *
113
+ * `keyOrOptions` may carry an optional `select` projection that maps the
114
+ * underlying `T` to a view `U`; the returned subscription's data shape
115
+ * widens accordingly. Without `select`, `U = T` and the projection
116
+ * computed is skipped.
82
117
  */
83
- export function createUse<Args extends unknown[], T>(
118
+ export function createUse<Args extends unknown[], T, U = T>(
84
119
  client: QueryClient,
85
120
  query: Query<Args, T>,
86
- keyOrOptions?: (() => Args) | UseOptions<Args>,
121
+ keyOrOptions?: (() => Args) | UseInternalOptions<Args, T, U>,
87
122
  ): {
88
- subscription: QuerySubscription<T>
123
+ subscription: QuerySubscription<U>
89
124
  dispose: () => void
90
125
  /** Suspend the subscription — release the entry (its refetchInterval +
91
126
  * focus/online listeners pause) without disposing it. Spec §4.1. */
@@ -100,8 +135,10 @@ export function createUse<Args extends unknown[], T>(
100
135
  const keyFn = typeof keyOrOptions === 'function' ? keyOrOptions : keyOrOptions?.key
101
136
  const enabledFn =
102
137
  typeof keyOrOptions === 'object' && keyOrOptions !== null ? keyOrOptions.enabled : undefined
138
+ const select =
139
+ typeof keyOrOptions === 'object' && keyOrOptions !== null ? keyOrOptions.select : undefined
103
140
 
104
- const sub = new SubscriptionImpl<T>(keepPreviousData)
141
+ const sub = new SubscriptionImpl<T, U>(keepPreviousData, select)
105
142
  let currentEntry: ClientEntry<T> | null = null
106
143
  let suspended = false
107
144
 
@@ -291,6 +328,9 @@ class InfiniteSubscriptionImpl<TPage, TItem> implements InfiniteQuerySubscriptio
291
328
  return cur.entry.firstValue()
292
329
  }
293
330
 
331
+ // Alias of firstValue() for Suspense / React 19 `use(...)` ergonomics.
332
+ promise = (): Promise<TPage[]> => this.firstValue()
333
+
294
334
  fetchNextPage = (): Promise<void> => {
295
335
  const cur = this.current$.peek()
296
336
  if (!cur) return Promise.resolve()
package/src/testing.ts CHANGED
@@ -47,6 +47,7 @@ export function fakeField<T>(
47
47
  reset: () => void
48
48
  markTouched: () => void
49
49
  revalidate: () => Promise<boolean>
50
+ setErrors: (errors: ReadonlyArray<string>) => void
50
51
  dispose: () => void
51
52
  }>,
52
53
  ): Field<T> {
@@ -85,6 +86,7 @@ export function fakeField<T>(
85
86
  reset: overrides?.reset ?? (() => value$.set(currentInitial)),
86
87
  markTouched: overrides?.markTouched ?? (() => touched$.set(true)),
87
88
  revalidate: overrides?.revalidate ?? (async () => errors$.peek().length === 0),
89
+ setErrors: overrides?.setErrors ?? ((errs) => errors$.set([...errs])),
88
90
  dispose: overrides?.dispose ?? (() => {}),
89
91
  }
90
92
  return fake
@@ -109,6 +111,7 @@ export function fakeAsyncState<T>(
109
111
  refetch: () => Promise<T>
110
112
  reset: () => void
111
113
  firstValue: () => Promise<T>
114
+ promise: () => Promise<T>
112
115
  }>,
113
116
  ): AsyncState<T> {
114
117
  const data$: ReadSignal<T | undefined> = signal(overrides?.data)
@@ -125,6 +128,7 @@ export function fakeAsyncState<T>(
125
128
  const refetch = overrides?.refetch ?? (async () => data$.peek() as T)
126
129
  const reset = overrides?.reset ?? (() => {})
127
130
  const firstValue = overrides?.firstValue ?? (async () => data$.peek() as T)
131
+ const promise = overrides?.promise ?? firstValue
128
132
 
129
133
  return {
130
134
  data: data$,
@@ -138,5 +142,6 @@ export function fakeAsyncState<T>(
138
142
  refetch,
139
143
  reset,
140
144
  firstValue,
145
+ promise,
141
146
  }
142
147
  }