@talelk/newui-data 0.0.0-stage → 1.0.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 (47) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +54 -2
  3. package/package.json +54 -3
  4. package/skills/newui-data/SKILL.md +116 -0
  5. package/src/download.test.ts +14 -0
  6. package/src/download.ts +39 -0
  7. package/src/exports.test.ts +105 -0
  8. package/src/form/form.test.tsx +102 -0
  9. package/src/form/form.tsx +85 -0
  10. package/src/form/index.ts +1 -0
  11. package/src/format.test.ts +22 -0
  12. package/src/format.ts +36 -0
  13. package/src/index.ts +4 -0
  14. package/src/query/client.tsx +53 -0
  15. package/src/query/collection.test.tsx +151 -0
  16. package/src/query/collection.ts +192 -0
  17. package/src/query/index.ts +3 -0
  18. package/src/query/repository.ts +30 -0
  19. package/src/resource/client.test.ts +173 -0
  20. package/src/resource/client.ts +307 -0
  21. package/src/resource/document.test.ts +151 -0
  22. package/src/resource/document.ts +198 -0
  23. package/src/resource/idb.test.ts +169 -0
  24. package/src/resource/idb.ts +167 -0
  25. package/src/resource/index.ts +70 -0
  26. package/src/resource/list.test.ts +150 -0
  27. package/src/resource/list.ts +230 -0
  28. package/src/resource/local.test.ts +111 -0
  29. package/src/resource/local.ts +155 -0
  30. package/src/resource/realtime.test.ts +331 -0
  31. package/src/resource/realtime.ts +287 -0
  32. package/src/resource/resource.test.ts +186 -0
  33. package/src/resource/resource.ts +213 -0
  34. package/src/resource/useCall.test.ts +60 -0
  35. package/src/resource/useCall.ts +74 -0
  36. package/src/resource/useDoc.test.ts +31 -0
  37. package/src/resource/useDoc.ts +23 -0
  38. package/src/resource/useDoctype.test.ts +104 -0
  39. package/src/resource/useDoctype.ts +128 -0
  40. package/src/resource/useList.test.ts +32 -0
  41. package/src/resource/useList.ts +22 -0
  42. package/src/resource/useNewDoc.test.ts +67 -0
  43. package/src/resource/useNewDoc.ts +78 -0
  44. package/src/select.test.ts +13 -0
  45. package/src/select.ts +19 -0
  46. package/src/undo.test.ts +24 -0
  47. package/src/undo.ts +37 -0
@@ -0,0 +1,198 @@
1
+ import { type Accessor, createSignal } from 'solid-js'
2
+ import type { RequestError } from './client'
3
+ import { createResource, type Resource, type ResourceFetcher } from './resource'
4
+
5
+ /** Per-operation overrides for a document resource. */
6
+ export interface DocumentOperationOptions<TResult> {
7
+ /** Overrides the default endpoint. */
8
+ url?: string
9
+ onSuccess?: (result: TResult) => void
10
+ onError?: (error: RequestError) => void
11
+ }
12
+
13
+ /** Options for {@link createDocumentResource}. */
14
+ export interface DocumentResourceOptions<TDoc = Record<string, unknown>> {
15
+ doctype: string
16
+ /** Document name, or an accessor for a reactive name. */
17
+ name: string | (() => string)
18
+ /** Fetch on creation. Defaults to `true`. */
19
+ auto?: boolean
20
+ /** Overrides the default get endpoint (`frappe.client.get`). */
21
+ url?: string
22
+ /** Map the raw document. */
23
+ transform?: (doc: TDoc) => TDoc
24
+ onSuccess?: (doc: TDoc) => void
25
+ onError?: (error: RequestError) => void
26
+ setValue?: DocumentOperationOptions<TDoc>
27
+ delete?: DocumentOperationOptions<unknown>
28
+ fetcher?: ResourceFetcher
29
+ }
30
+
31
+ /** A single-document resource with local edits and dirty tracking. */
32
+ export interface DocumentResource<TDoc = Record<string, unknown>> {
33
+ doctype: string
34
+ name: Accessor<string>
35
+ doc: Accessor<TDoc | null>
36
+ /** The last saved/loaded copy, for dirty comparison. */
37
+ originalDoc: Accessor<TDoc | null>
38
+ isDirty: Accessor<boolean>
39
+ loading: Accessor<boolean>
40
+ error: Accessor<RequestError | null>
41
+ fetched: Accessor<boolean>
42
+ /** The underlying GET resource. */
43
+ get: Resource<TDoc>
44
+ /** POST the changed fields to `frappe.client.set_value`. */
45
+ save: () => Promise<TDoc | null>
46
+ /** POST explicit `fieldname` values. */
47
+ setValue: (values: Record<string, unknown>) => Promise<TDoc | null>
48
+ /** POST to `frappe.client.delete` and clear the doc. */
49
+ delete: () => Promise<unknown>
50
+ /** Refetch the document. */
51
+ reload: () => Promise<TDoc | null>
52
+ /** Edit the document locally. */
53
+ setDoc: (doc: TDoc | ((current: TDoc | null) => TDoc)) => void
54
+ }
55
+
56
+ const GET_URL = 'frappe.client.get'
57
+ const SET_VALUE_URL = 'frappe.client.set_value'
58
+ const DELETE_URL = 'frappe.client.delete'
59
+
60
+ function cloneDoc<TDoc>(doc: TDoc | null): TDoc | null {
61
+ if (doc === null) return null
62
+ return JSON.parse(JSON.stringify(doc)) as TDoc
63
+ }
64
+
65
+ /**
66
+ * The signal-based replacement for frappe-ui's `createDocumentResource`.
67
+ *
68
+ * `doc` is a local signal; `setDoc` edits it, `isDirty` compares it to
69
+ * `originalDoc`, and `save` sends only the changed fields (skipping the
70
+ * network entirely when nothing changed). `setValue`/`delete` write through.
71
+ */
72
+ export function createDocumentResource<TDoc = Record<string, unknown>>(
73
+ options: DocumentResourceOptions<TDoc>,
74
+ ): DocumentResource<TDoc> {
75
+ const name = (): string =>
76
+ typeof options.name === 'function' ? options.name() : options.name
77
+
78
+ const [doc, setDocSignal] = createSignal<TDoc | null>(null)
79
+ const [originalDoc, setOriginalDoc] = createSignal<TDoc | null>(null)
80
+
81
+ const transform = options.transform
82
+ const mapDoc = transform
83
+ ? (data: unknown) => transform(data as TDoc)
84
+ : undefined
85
+
86
+ const get = createResource<TDoc>({
87
+ url: options.url ?? GET_URL,
88
+ method: 'GET',
89
+ params: () => ({ doctype: options.doctype, name: name() }),
90
+ transform: mapDoc,
91
+ fetcher: options.fetcher,
92
+ onSuccess: (data) => {
93
+ setDocSignal(() => data)
94
+ setOriginalDoc(() => cloneDoc(data))
95
+ options.onSuccess?.(data)
96
+ },
97
+ onError: (error) => {
98
+ setDocSignal(() => null)
99
+ setOriginalDoc(() => null)
100
+ options.onError?.(error)
101
+ },
102
+ })
103
+
104
+ const setValueResource = createResource<TDoc, Record<string, unknown>>({
105
+ url: options.setValue?.url ?? SET_VALUE_URL,
106
+ method: 'POST',
107
+ makeParams: (input) => ({
108
+ doctype: options.doctype,
109
+ name: name(),
110
+ fieldname: input,
111
+ }),
112
+ transform: mapDoc,
113
+ fetcher: options.fetcher,
114
+ onSuccess: (data) => {
115
+ setDocSignal(() => data)
116
+ setOriginalDoc(() => cloneDoc(data))
117
+ options.setValue?.onSuccess?.(data)
118
+ },
119
+ onError: (error) => {
120
+ // Roll the local edit back to the last saved copy.
121
+ setDocSignal(() => originalDoc())
122
+ options.setValue?.onError?.(error)
123
+ },
124
+ })
125
+
126
+ const deleteResource = createResource<unknown>({
127
+ url: options.delete?.url ?? DELETE_URL,
128
+ method: 'POST',
129
+ params: () => ({ doctype: options.doctype, name: name() }),
130
+ fetcher: options.fetcher,
131
+ onSuccess: (data) => {
132
+ setDocSignal(() => null)
133
+ setOriginalDoc(() => null)
134
+ options.delete?.onSuccess?.(data)
135
+ },
136
+ onError: (error) => options.delete?.onError?.(error),
137
+ })
138
+
139
+ const isDirty = (): boolean =>
140
+ JSON.stringify(doc() ?? null) !== JSON.stringify(originalDoc() ?? null)
141
+
142
+ function changedFields(): Record<string, unknown> {
143
+ const current = (cloneDoc(doc()) ?? {}) as Record<string, unknown>
144
+ const original = (originalDoc() ?? {}) as Record<string, unknown>
145
+ const values: Record<string, unknown> = {}
146
+ for (const key in current) {
147
+ if (JSON.stringify(current[key]) !== JSON.stringify(original[key])) {
148
+ values[key] = current[key]
149
+ }
150
+ }
151
+ // Identity/meta fields are never writable through `set_value`.
152
+ delete values['doctype']
153
+ delete values['name']
154
+ return values
155
+ }
156
+
157
+ function setValue(values: Record<string, unknown>): Promise<TDoc | null> {
158
+ return setValueResource.submit(values)
159
+ }
160
+
161
+ function save(): Promise<TDoc | null> {
162
+ const values = changedFields()
163
+ if (Object.keys(values).length === 0) return Promise.resolve(doc())
164
+ return setValue(values)
165
+ }
166
+
167
+ function setDoc(next: TDoc | ((current: TDoc | null) => TDoc)): void {
168
+ if (typeof next === 'function') {
169
+ const updater = next as (current: TDoc | null) => TDoc
170
+ setDocSignal(() => updater(doc()))
171
+ return
172
+ }
173
+ setDocSignal(() => next)
174
+ }
175
+
176
+ function reload(): Promise<TDoc | null> {
177
+ return get.reload()
178
+ }
179
+
180
+ if (options.auto !== false) void reload()
181
+
182
+ return {
183
+ doctype: options.doctype,
184
+ name,
185
+ doc,
186
+ originalDoc,
187
+ isDirty,
188
+ loading: get.loading,
189
+ error: get.error,
190
+ fetched: get.fetched,
191
+ get,
192
+ save,
193
+ setValue,
194
+ delete: () => deleteResource.submit(),
195
+ reload,
196
+ setDoc,
197
+ }
198
+ }
@@ -0,0 +1,169 @@
1
+ import { describe, expect, it } from 'vitest'
2
+ import { createIdbStore } from './idb'
3
+
4
+ /**
5
+ * jsdom ships no IndexedDB, so the native code path is driven by a minimal
6
+ * in-memory `IDBFactory` implementing exactly the subset `createIdbStore`
7
+ * uses: open + upgrade, one object store, and get/put/delete/getAllKeys/clear.
8
+ * Requests settle on a microtask, as the real API does, which is why the store
9
+ * must attach its handlers after the request is created.
10
+ */
11
+
12
+ interface FakeRequest {
13
+ result: unknown
14
+ error: DOMException | Error | null
15
+ onsuccess: ((event: Event) => void) | null
16
+ onerror: ((event: Event) => void) | null
17
+ onupgradeneeded: ((event: Event) => void) | null
18
+ }
19
+
20
+ function makeRequest(work: () => unknown): FakeRequest {
21
+ const request: FakeRequest = {
22
+ result: undefined,
23
+ error: null,
24
+ onsuccess: null,
25
+ onerror: null,
26
+ onupgradeneeded: null,
27
+ }
28
+ queueMicrotask(() => {
29
+ try {
30
+ request.result = work()
31
+ request.onsuccess?.(new Event('success'))
32
+ } catch (error) {
33
+ request.error = error as Error
34
+ request.onerror?.(new Event('error'))
35
+ }
36
+ })
37
+ return request
38
+ }
39
+
40
+ function makeFactory(): { factory: IDBFactory; opens: () => number } {
41
+ const data = new Map<IDBValidKey, unknown>()
42
+ const stores = new Set<string>()
43
+ let opens = 0
44
+
45
+ const objectStore = {
46
+ get: (key: IDBValidKey) => makeRequest(() => data.get(key)),
47
+ put: (value: unknown, key: IDBValidKey) =>
48
+ makeRequest(() => {
49
+ data.set(key, value)
50
+ return key
51
+ }),
52
+ delete: (key: IDBValidKey) =>
53
+ makeRequest(() => {
54
+ data.delete(key)
55
+ return undefined
56
+ }),
57
+ getAllKeys: () => makeRequest(() => [...data.keys()]),
58
+ clear: () =>
59
+ makeRequest(() => {
60
+ data.clear()
61
+ return undefined
62
+ }),
63
+ }
64
+
65
+ const db = {
66
+ objectStoreNames: { contains: (name: string) => stores.has(name) },
67
+ createObjectStore: (name: string) => {
68
+ stores.add(name)
69
+ return {}
70
+ },
71
+ transaction: () => ({ objectStore: () => objectStore }),
72
+ close: () => {},
73
+ }
74
+
75
+ const factory = {
76
+ open: () => {
77
+ opens += 1
78
+ const request: FakeRequest = {
79
+ result: db,
80
+ error: null,
81
+ onsuccess: null,
82
+ onerror: null,
83
+ onupgradeneeded: null,
84
+ }
85
+ queueMicrotask(() => {
86
+ request.onupgradeneeded?.(new Event('upgradeneeded'))
87
+ request.onsuccess?.(new Event('success'))
88
+ })
89
+ return request as unknown as IDBOpenDBRequest
90
+ },
91
+ }
92
+
93
+ return { factory: factory as unknown as IDBFactory, opens: () => opens }
94
+ }
95
+
96
+ describe('createIdbStore', () => {
97
+ it('stores and reads structured-clone values through IndexedDB', async () => {
98
+ const { factory } = makeFactory()
99
+ const store = createIdbStore({ factory })
100
+
101
+ expect(store.persistent).toBe(true)
102
+ await store.set('doc', { name: 'T-1', tags: ['a', 'b'] })
103
+ expect(await store.get('doc')).toEqual({ name: 'T-1', tags: ['a', 'b'] })
104
+ expect(await store.get('missing')).toBe(null)
105
+ })
106
+
107
+ it('lists, deletes and clears keys', async () => {
108
+ const { factory } = makeFactory()
109
+ const store = createIdbStore({ factory })
110
+
111
+ await store.set('a', 1)
112
+ await store.set('b', 2)
113
+ expect((await store.keys()).sort()).toEqual(['a', 'b'])
114
+
115
+ await store.delete('a')
116
+ expect(await store.keys()).toEqual(['b'])
117
+
118
+ await store.clear()
119
+ expect(await store.keys()).toEqual([])
120
+ })
121
+
122
+ it('opens the database once and reuses it', async () => {
123
+ const { factory, opens } = makeFactory()
124
+ const store = createIdbStore({ factory })
125
+
126
+ await store.set('a', 1)
127
+ await store.get('a')
128
+ await store.set('b', 2)
129
+ expect(opens()).toBe(1)
130
+ })
131
+
132
+ it('falls back to memory when IndexedDB is unavailable', async () => {
133
+ const store = createIdbStore({ factory: null })
134
+
135
+ expect(store.persistent).toBe(false)
136
+ await store.set('k', { v: 1 })
137
+ expect(await store.get('k')).toEqual({ v: 1 })
138
+ expect(await store.keys()).toEqual(['k'])
139
+ await store.delete('k')
140
+ expect(await store.get('k')).toBe(null)
141
+ await store.clear()
142
+ expect(await store.keys()).toEqual([])
143
+ })
144
+
145
+ it('reports non-persistence when there is no global indexedDB', () => {
146
+ // jsdom does not implement IndexedDB, so the default is the memory store.
147
+ const store = createIdbStore()
148
+ expect(store.persistent).toBe(false)
149
+ })
150
+
151
+ it('rejects when the database fails to open', async () => {
152
+ const failing = {
153
+ open: () => {
154
+ const request: FakeRequest = {
155
+ result: undefined,
156
+ error: new Error('permission denied'),
157
+ onsuccess: null,
158
+ onerror: null,
159
+ onupgradeneeded: null,
160
+ }
161
+ queueMicrotask(() => request.onerror?.(new Event('error')))
162
+ return request
163
+ },
164
+ } as unknown as IDBFactory
165
+ const store = createIdbStore({ factory: failing })
166
+
167
+ await expect(store.get('x')).rejects.toThrow('permission denied')
168
+ })
169
+ })
@@ -0,0 +1,167 @@
1
+ /**
2
+ * An IndexedDB-backed, Promise-based key/value store for caching resource data.
3
+ *
4
+ * The asynchronous sibling of {@link createLocalStore}, written against the
5
+ * native `indexedDB` API — no `idb`/`idb-keyval` dependency. Values are stored
6
+ * with the structured-clone algorithm, so objects, arrays, `Date`s, `Blob`s and
7
+ * the like round-trip without an explicit `JSON.stringify`.
8
+ *
9
+ * SSR-safe: with no `indexedDB` (the server, or a browser with it disabled) the
10
+ * store falls back to an in-memory `Map` and every method still resolves, so a
11
+ * caller never has to branch on `typeof window`. The `indexedDB` implementation
12
+ * is injectable through {@link IdbStoreOptions.factory}, which is also how the
13
+ * unit tests drive the native code path (jsdom ships no IndexedDB).
14
+ */
15
+
16
+ /** A Promise-based, structured-clone key/value cache. */
17
+ export interface IdbStore<T = unknown> {
18
+ /** The stored value, or `null` when the key is absent. */
19
+ get(key: string): Promise<T | null>
20
+ /** Persist a value. */
21
+ set(key: string, value: T): Promise<void>
22
+ /** Remove a key. */
23
+ delete(key: string): Promise<void>
24
+ /** Every key currently in the store. */
25
+ keys(): Promise<string[]>
26
+ /** Remove every key. */
27
+ clear(): Promise<void>
28
+ /** False when the store fell back to memory (SSR, or no IndexedDB). */
29
+ readonly persistent: boolean
30
+ /** Close the underlying database, if one was opened. */
31
+ close(): void
32
+ }
33
+
34
+ /** Options for {@link createIdbStore}. */
35
+ export interface IdbStoreOptions {
36
+ /** Database name. Defaults to `newui`. */
37
+ name?: string
38
+ /** Object store name. Defaults to `keyval`. */
39
+ store?: string
40
+ /** Database version. Defaults to `1`. */
41
+ version?: number
42
+ /** IndexedDB implementation. Defaults to the global `indexedDB`. */
43
+ factory?: IDBFactory | null
44
+ }
45
+
46
+ function defaultFactory(): IDBFactory | null {
47
+ try {
48
+ return typeof indexedDB === 'undefined' ? null : indexedDB
49
+ } catch {
50
+ return null
51
+ }
52
+ }
53
+
54
+ /** Resolve an {@link IDBRequest} on success and reject on error. */
55
+ function promisify<T>(request: IDBRequest<T>): Promise<T> {
56
+ return new Promise<T>((resolve, reject) => {
57
+ request.onsuccess = () => resolve(request.result)
58
+ request.onerror = () =>
59
+ reject(request.error ?? new Error('IndexedDB request failed'))
60
+ })
61
+ }
62
+
63
+ /**
64
+ * Create a Promise-based cache over IndexedDB.
65
+ *
66
+ * The database is opened lazily on the first operation and reused. When no
67
+ * IndexedDB implementation is available the store keeps values in memory, so
68
+ * `get`/`set`/`keys` behave identically (minus persistence) and `persistent`
69
+ * reports the difference.
70
+ */
71
+ export function createIdbStore<T = unknown>(
72
+ options: IdbStoreOptions = {},
73
+ ): IdbStore<T> {
74
+ const dbName = options.name ?? 'newui'
75
+ const storeName = options.store ?? 'keyval'
76
+ const version = options.version ?? 1
77
+ const factory =
78
+ options.factory === undefined ? defaultFactory() : options.factory
79
+ const memory = new Map<string, T>()
80
+
81
+ let opening: Promise<IDBDatabase> | null = null
82
+
83
+ function open(): Promise<IDBDatabase> {
84
+ if (!factory) {
85
+ return Promise.reject(new Error('IndexedDB is not available'))
86
+ }
87
+ if (opening) return opening
88
+ opening = new Promise<IDBDatabase>((resolve, reject) => {
89
+ const request = factory.open(dbName, version)
90
+ request.onupgradeneeded = () => {
91
+ const db = request.result
92
+ if (!db.objectStoreNames.contains(storeName)) {
93
+ db.createObjectStore(storeName)
94
+ }
95
+ }
96
+ request.onsuccess = () => resolve(request.result)
97
+ request.onerror = () =>
98
+ reject(
99
+ request.error ?? new Error(`Failed to open IndexedDB "${dbName}"`),
100
+ )
101
+ })
102
+ return opening
103
+ }
104
+
105
+ async function store(mode: IDBTransactionMode): Promise<IDBObjectStore> {
106
+ const db = await open()
107
+ return db.transaction(storeName, mode).objectStore(storeName)
108
+ }
109
+
110
+ async function get(key: string): Promise<T | null> {
111
+ if (!factory) return memory.get(key) ?? null
112
+ const objectStore = await store('readonly')
113
+ const value = (await promisify(objectStore.get(key))) as T | undefined
114
+ return value ?? null
115
+ }
116
+
117
+ async function set(key: string, value: T): Promise<void> {
118
+ if (!factory) {
119
+ memory.set(key, value)
120
+ return
121
+ }
122
+ const objectStore = await store('readwrite')
123
+ await promisify(objectStore.put(value, key))
124
+ }
125
+
126
+ async function remove(key: string): Promise<void> {
127
+ if (!factory) {
128
+ memory.delete(key)
129
+ return
130
+ }
131
+ const objectStore = await store('readwrite')
132
+ await promisify(objectStore.delete(key))
133
+ }
134
+
135
+ async function keys(): Promise<string[]> {
136
+ if (!factory) return [...memory.keys()]
137
+ const objectStore = await store('readonly')
138
+ const all = await promisify(objectStore.getAllKeys())
139
+ return all.map(String)
140
+ }
141
+
142
+ async function clear(): Promise<void> {
143
+ if (!factory) {
144
+ memory.clear()
145
+ return
146
+ }
147
+ const objectStore = await store('readwrite')
148
+ await promisify(objectStore.clear())
149
+ }
150
+
151
+ function close(): void {
152
+ const current = opening
153
+ if (!current) return
154
+ opening = null
155
+ void current.then((db) => db.close()).catch(() => {})
156
+ }
157
+
158
+ return {
159
+ get,
160
+ set,
161
+ delete: remove,
162
+ keys,
163
+ clear,
164
+ persistent: factory !== null,
165
+ close,
166
+ }
167
+ }
@@ -0,0 +1,70 @@
1
+ /**
2
+ * The signal-based data layer: a configurable request client, generic
3
+ * resources, and the thin composables over them.
4
+ *
5
+ * Ported from frappe-ui's `resources/` + `data-fetching/`, translated to Solid
6
+ * signals. No Vue `ref`/`reactive`, no `v-model` — every value is an accessor
7
+ * (`() => T`) and every action a plain function.
8
+ */
9
+
10
+ export type {
11
+ ClientConfig,
12
+ RequestErrorOptions,
13
+ RequestOptions,
14
+ } from './client'
15
+ export {
16
+ getConfig,
17
+ isAbortError,
18
+ normalizeError,
19
+ RequestError,
20
+ request,
21
+ resetConfig,
22
+ setConfig,
23
+ } from './client'
24
+ export type {
25
+ DocumentOperationOptions,
26
+ DocumentResource,
27
+ DocumentResourceOptions,
28
+ } from './document'
29
+ export { createDocumentResource } from './document'
30
+ export type { IdbStore, IdbStoreOptions } from './idb'
31
+ export { createIdbStore } from './idb'
32
+ export type {
33
+ ListFilters,
34
+ ListResource,
35
+ ListResourceOptions,
36
+ } from './list'
37
+ export { createListResource } from './list'
38
+ export type { LocalStore, LocalStoreOptions } from './local'
39
+ export { createLocalStore } from './local'
40
+ export type {
41
+ Realtime,
42
+ RealtimeEventMap,
43
+ RealtimeListener,
44
+ RealtimeOptions,
45
+ RealtimeSocket,
46
+ RealtimeSocketFactory,
47
+ } from './realtime'
48
+ export { createRealtime } from './realtime'
49
+ export type {
50
+ Resource,
51
+ ResourceFetcher,
52
+ ResourceOptions,
53
+ } from './resource'
54
+ export { createResource } from './resource'
55
+ export type { UseCallOptions, UseCallReturn } from './useCall'
56
+ export { useCall } from './useCall'
57
+ export type { UseDocOptions, UseDocReturn } from './useDoc'
58
+ export { useDoc } from './useDoc'
59
+ export type {
60
+ DoctypeAction,
61
+ RunDocMethodInput,
62
+ RunMethodInput,
63
+ UseDoctypeOptions,
64
+ UseDoctypeReturn,
65
+ } from './useDoctype'
66
+ export { useDoctype } from './useDoctype'
67
+ export type { UseListOptions, UseListReturn } from './useList'
68
+ export { useList } from './useList'
69
+ export type { UseNewDocOptions, UseNewDocReturn } from './useNewDoc'
70
+ export { useNewDoc } from './useNewDoc'