mirror of
https://github.com/supabase/supabase.git
synced 2026-10-09 19:35:06 +03:00
## Summary Implements the "notebook query and mutation hooks" step of the notebooks data layer: - `data/content/notebooks/notebook-query.ts` — `getNotebook`/`useNotebookQuery`, wrapping the existing `getContentById` and narrowing to `type: 'notebook'`. - `data/content/notebooks/notebooks-infinite-query.ts` — `useNotebooksInfiniteQuery`, a typed wrapper over `useContentInfiniteQuery` narrowing pages to notebook rows. - `data/content/notebooks/notebook-upsert-mutation.ts` — `createNotebook`/`updateNotebook` + their mutation hooks, PUTting through the existing `upsertContent`. Write-path correctness, worked out while building the mutation hooks: - Cell `id`s are always backend-generated, never client-supplied — a brand-new cell has no `id` at all; an existing cell being kept/edited in an update keeps its real id so the backend can diff it against the previous version. `notebook-schema.ts` gains `writableCellSchema`/`writableNotebookSchema` (ids optional per cell) and `WritableCell`/`WritableNotebook` types, derived from `z.infer` of those schemas rather than hand-duplicated, with only the `sql` field re-branded per cell type via a small distributive conditional type. - Cell SQL at this write boundary must already be `SafeSqlFragment`/`SafeLogSqlFragment` (proven user-authored at a save/run event handler), not `unchecked_sql` — matching the `safe-sql-execution` skill's provenance model. - `content-remap.ts`'s notebook `unmapSqlContentField` branch is simplified to a passthrough: notebook writes only ever arrive already wire-shaped via `createNotebook`/`updateNotebook`, so there's nothing left to unmap. Note: this was originally stacked on `feature/notebooks-types-convergence`, but that branch merged into `master` (#48905) while this PR was in progress, so it's rebased directly onto `master` now. ## Test plan - [x] `pnpm --filter studio run typecheck` passes - [x] `pnpm --filter studio exec vitest run data/content/notebooks data/content/content-remap.test.ts` — 38/38 passing - [x] `pnpm --filter studio exec eslint` clean on all touched files <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added notebook listing with pagination, filtering, sorting, and project-specific queries. * Added notebook retrieval for viewing individual notebooks. * Added notebook creation and editing with automatic content refresh. * Added support for preserving cell IDs and safely handling SQL content. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
41 lines
1.5 KiB
TypeScript
41 lines
1.5 KiB
TypeScript
import { useQuery } from '@tanstack/react-query'
|
|
|
|
import { getContentById } from '../content-id-query'
|
|
import { contentKeys } from '../keys'
|
|
import type { Notebooks, ResponseError, UseCustomQueryOptions } from '@/types'
|
|
|
|
export type NotebookVariables = { projectRef?: string; id?: string }
|
|
export type NotebookError = ResponseError
|
|
|
|
export async function getNotebook(
|
|
{ projectRef, id }: NotebookVariables,
|
|
signal?: AbortSignal,
|
|
headers?: HeadersInit
|
|
) {
|
|
const data = await getContentById({ projectRef, id }, signal, headers)
|
|
|
|
// api-types doesn't have 'notebook' in GetUserContentByIdResponse['type'] yet — same gap
|
|
// tracked by the ContentBase TODO in content-query.ts — so this narrowing can't be static.
|
|
if ((data.type as string) !== 'notebook') {
|
|
throw new Error(`Content ${id} is not a notebook (got type: ${data.type})`)
|
|
}
|
|
|
|
return data as unknown as Omit<typeof data, 'type' | 'content'> & {
|
|
type: 'notebook'
|
|
content: Notebooks.Content
|
|
}
|
|
}
|
|
|
|
export type NotebookData = Awaited<ReturnType<typeof getNotebook>>
|
|
|
|
export const useNotebookQuery = <TData = NotebookData>(
|
|
{ projectRef, id }: NotebookVariables,
|
|
{ enabled = true, ...options }: UseCustomQueryOptions<NotebookData, NotebookError, TData> = {}
|
|
) =>
|
|
useQuery<NotebookData, NotebookError, TData>({
|
|
queryKey: contentKeys.resource(projectRef, id),
|
|
queryFn: ({ signal }) => getNotebook({ projectRef, id }, signal),
|
|
enabled: enabled && typeof projectRef !== 'undefined' && typeof id !== 'undefined',
|
|
...options,
|
|
})
|