-
Notifications
You must be signed in to change notification settings - Fork 55
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[draft] The end of undefined docSync() / await doc() #402
base: main
Are you sure you want to change the base?
Changes from all commits
50cdcf0
e510bb5
9fdda55
72e6490
6ddc2b2
858bd27
87d8fb6
4be34ba
5fa277f
70ed5dc
1f0c7a0
496fdc5
4da60ad
5bc01a8
c61e7e4
f548d88
1f73b8b
bc4fdcf
da5ed07
4d5f507
c584221
4ccd975
c32039a
169b11d
5e7d3d1
4d4c763
ee74e58
4b90c35
e880590
5b91e7b
31b6d96
65ffd20
0f787e3
95f4882
f61d420
98f40a4
73f0838
f4258e6
d568155
5015c44
485f4c7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
import { AnyDocumentId, DocHandle } from "@automerge/automerge-repo/slim" | ||
import { wrapPromise } from "./wrapPromise.js" | ||
import { useRepo } from "./useRepo.js" | ||
import { useEffect, useRef, useState } from "react" | ||
|
||
// Shared with useDocHandles | ||
export const wrapperCache = new Map< | ||
AnyDocumentId, | ||
ReturnType<typeof wrapPromise> | ||
>() | ||
|
||
interface UseDocHandleSuspendingParams { | ||
suspense: true | ||
} | ||
interface UseDocHandleSynchronousParams { | ||
suspense: false | ||
} | ||
|
||
type UseDocHandleParams = | ||
| UseDocHandleSuspendingParams | ||
| UseDocHandleSynchronousParams | ||
|
||
export function useDocHandle<T>( | ||
id: AnyDocumentId, | ||
params: UseDocHandleSuspendingParams | ||
): DocHandle<T> | ||
export function useDocHandle<T>( | ||
id: AnyDocumentId, | ||
params?: UseDocHandleSynchronousParams | ||
): DocHandle<T> | undefined | ||
export function useDocHandle<T>( | ||
id: AnyDocumentId, | ||
{ suspense }: UseDocHandleParams = { suspense: false } | ||
): DocHandle<T> | undefined { | ||
const repo = useRepo() | ||
const controllerRef = useRef<AbortController>() | ||
const [handle, setHandle] = useState<DocHandle<T> | undefined>() | ||
|
||
let wrapper = wrapperCache.get(id) | ||
if (!wrapper) { | ||
controllerRef.current?.abort() | ||
controllerRef.current = new AbortController() | ||
|
||
const promise = repo.find<T>(id, { signal: controllerRef.current.signal }) | ||
wrapper = wrapPromise(promise) | ||
wrapperCache.set(id, wrapper) | ||
} | ||
|
||
useEffect(() => { | ||
if (suspense === false) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this would be simpler as if (suspense) {
return
} and then handle the non-suspense case unconditionally. |
||
void wrapper.promise | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah, this is to deal with floating promises? But you have a |
||
.then(handle => { | ||
setHandle(handle as DocHandle<T>) | ||
}) | ||
.catch(e => { | ||
console.log("handle promise caught", e) | ||
setHandle(undefined) | ||
}) | ||
} | ||
}, [suspense, wrapper]) | ||
|
||
if (suspense) { | ||
return wrapper.read() as DocHandle<T> | ||
} else { | ||
return handle || undefined | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What does |
||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
import { AutomergeUrl, DocHandle } from "@automerge/automerge-repo/slim" | ||
import { useState, useEffect } from "react" | ||
import { useRepo } from "./useRepo.js" | ||
import { PromiseWrapper, wrapPromise } from "./wrapPromise.js" | ||
import { wrapperCache } from "./useDocHandle.js" | ||
|
||
interface UseDocHandlesParams { | ||
suspense?: boolean | ||
} | ||
|
||
type DocHandleMap<T> = Map<AutomergeUrl, DocHandle<T> | undefined> | ||
|
||
export function useDocHandles<T>( | ||
ids: AutomergeUrl[], | ||
{ suspense = false }: UseDocHandlesParams = {} | ||
): DocHandleMap<T> { | ||
const repo = useRepo() | ||
const [handleMap, setHandleMap] = useState<DocHandleMap<T>>(() => new Map()) | ||
|
||
const pendingPromises: PromiseWrapper<DocHandle<T>>[] = [] | ||
const nextHandleMap = new Map<AutomergeUrl, DocHandle<T> | undefined>() | ||
|
||
// Check if we need any new wrappers | ||
for (const id of ids) { | ||
let wrapper = wrapperCache.get(id)! | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why |
||
if (!wrapper) { | ||
try { | ||
const promise = repo.find<T>(id) | ||
wrapper = wrapPromise(promise) | ||
wrapperCache.set(id, wrapper) | ||
} catch (e) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What is this catching? |
||
continue | ||
} | ||
} | ||
|
||
// Try to read each wrapper. | ||
// Update handleMap with any available handles, | ||
// and collect any pending promises | ||
try { | ||
const handle = wrapper.read() as DocHandle<T> | ||
nextHandleMap.set(id, handle) | ||
} catch (e) { | ||
if (e instanceof Promise) { | ||
pendingPromises.push(wrapper as PromiseWrapper<DocHandle<T>>) | ||
} else { | ||
nextHandleMap.set(id, undefined) | ||
} | ||
} | ||
} | ||
|
||
// If any promises are pending, suspend with Promise.all | ||
if (suspense && pendingPromises.length > 0) { | ||
throw Promise.all(pendingPromises.map(p => p.promise)) | ||
} | ||
|
||
useEffect(() => { | ||
if (pendingPromises.length > 0) { | ||
void Promise.allSettled(pendingPromises.map(p => p.promise)).then( | ||
handles => { | ||
handles.forEach(r => { | ||
if (r.status === "fulfilled") { | ||
const h = r.value as DocHandle<T> | ||
nextHandleMap.set(h.url, h) | ||
} | ||
}) | ||
setHandleMap(nextHandleMap) | ||
} | ||
) | ||
} else { | ||
setHandleMap(nextHandleMap) | ||
} | ||
}, [suspense, ids]) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You're omitting |
||
|
||
return handleMap | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we really want this global, and not, e.g., living in something that also wraps a
RepoContext.Provider
?