47 lines
2.0 KiB
TypeScript
47 lines
2.0 KiB
TypeScript
"use client"
|
|
|
|
// The project's notes, live (umbrella TS6.4 and TS6.6): the page's view renders the first page and hands it
|
|
// over as the seed, so the page arrives complete; this keeps it current through the organization's live stream
|
|
// -- a note another member adds, renames or deletes appears without a reload. The rows come from
|
|
// `notes.note.getMany`, under the same permission as the list, so the stream itself never carries a note's
|
|
// contents.
|
|
|
|
import type { ProjectSlugs } from "@conusai-kit/ui"
|
|
import { liveCollection, useDataClient, useLive } from "@conusai-kit/ui/data"
|
|
// Its live sync stops when no live query reads it any more (TanStack DB's collection GC).
|
|
import { useState } from "react"
|
|
import { NOTE_COLLECTION, type NoteView } from "../contract/index.js"
|
|
|
|
export function useLiveNotes(slugs: ProjectSlugs, seed: readonly NoteView[]): readonly NoteView[] {
|
|
const client = useDataClient()
|
|
// One collection for the life of the page: a page of another project mounts its own, and the seed is the
|
|
// first render's.
|
|
const [notes] = useState(() =>
|
|
liveCollection<NoteView>({
|
|
client,
|
|
org: slugs.org,
|
|
topic: { ws: slugs.workspace, project: slugs.project },
|
|
entity: NOTE_COLLECTION.entity,
|
|
key: NOTE_COLLECTION.key,
|
|
version: "version",
|
|
seed,
|
|
list: {
|
|
procedure: NOTE_COLLECTION.list,
|
|
input: { ...slugs },
|
|
rows: (out) => (out as { notes: NoteView[] }).notes,
|
|
},
|
|
getMany: {
|
|
procedure: NOTE_COLLECTION.getMany,
|
|
input: { ...slugs },
|
|
rows: (out) => (out as { items: NoteView[] }).items,
|
|
},
|
|
}),
|
|
)
|
|
// Newest first, as the list procedure orders them, through the query operators (umbrella §7.2).
|
|
const { data, isReady } = useLive(notes, (q, rows) =>
|
|
q.from({ note: rows }).orderBy(({ note }: { note: NoteView }) => note.createdAt, "desc"),
|
|
)
|
|
// Until the collection is ready the server's rows are the answer, so the first paint matches it.
|
|
return isReady ? data : seed
|
|
}
|