db/cell_queries.ts

Raw queries against the cell table.

Convention: deps: QueryDeps first, no audit side effects, mutations return the affected row (or null for not-found).

cell.refs is auto-extracted from data on every create and update via fact_hash_extract_refs (depth-first walk for blake3:-prefixed strings). Callers never pass refs directly — the column is a derived projection of data for cells-by-fact discovery, mirroring what a fact store does for JSON facts.

Soft delete via deleted_at. All get / list queries exclude tombstones by default; include_deleted: true opts in for admin / audit views.

path uniqueness is global, enforced by idx_cell_path_unique (partial on active rows). Path reuse after soft delete falls out of the partial index — queries do not need special handling.

view source

Declarations
#

15 declarations

CellCreateQueryInput
#

db/cell_queries.ts view source

CellCreateQueryInput import type {CellCreateQueryInput} from '@fuzdev/fuz_app/db/cell_queries.js';

Input for query_cell_create. refs is derived from data. kind is the write-once capability axis (cell.kind column) — set here at INSERT and absent from CellUpdatePatch, so it can never change post-create. parent_id / root_id / moderation are the directory-tree + lifecycle columns the handler derives (parent containment, governing root, the create-authorizer verdict) — likewise write-once / control-gated, never on CellUpdatePatch.

data

type Json

kind?

type string | null

visibility?

type CellVisibility

path?

type string | null

parent_id?

type Uuid | null

root_id?

type Uuid | null

moderation?

type string | null

created_by?

type Uuid | null

CellListOptions
#

db/cell_queries.ts view source

CellListOptions import type {CellListOptions} from '@fuzdev/fuz_app/db/cell_queries.js';

Common pagination + tombstone-visibility options for list queries.

limit?

type number

offset?

type number

include_deleted?

type boolean

CellListParams
#

db/cell_queries.ts view source

CellListParams import type {CellListParams} from '@fuzdev/fuz_app/db/cell_queries.js';

Parameters for query_cell_list. All filter dimensions are optional.

kind?

Match cell.kind = ? (uses idx_cell_kind).

type string

visibility?

Match cell.visibility = ? directly on the top-level column. Additional narrowing on top of the SQL-side auth visibility predicate — useful for the public discovery feed where authed callers must NOT see their own private entries mixed in.

type CellVisibility

ref?

Match cells whose refs[] contains this hash (uses idx_cell_refs).

type FactHash

created_by?

Filter to cells created by this actor (uses idx_cell_created_by).

type Uuid

path_prefix?

Filter to cells whose path starts with this prefix. Wildcard metachars in the prefix are NOT special — starts_with() does literal matching.

type string

root_id?

Scope to a directory subtree by governing root (cell.root_id = ?, uses idx_cell_root).

type Uuid

moderation?

Filter by moderation lifecycle marker ('pending' drives the mod queue via idx_cell_moderation_pending).

Note: the visibility predicate still applies, so a 'pending' (private) queue surfaces only to viewers it admits — admin / owner / grant. A non-admin container manager won't see pending children here until a manager-scoped visibility branch is added, so for now the queue is an admin surface (cell_moderate of a known id still works regardless).

type string

ids?

Batch-fetch by id. The visibility predicate still runs, so callers passing ids they can't view simply get fewer rows back. Order of the returned rows follows order_by / order_direction, not the input list — callers that need positional output (e.g. preserving a collection's items[] order) should re-index client-side.

type Array<Uuid>

viewer_actor_id

Viewer actor for the visibility predicate. Pass null for unauthenticated callers — only cell.visibility === 'public' rows are admitted then.

type Uuid | null

viewer_is_admin

When true, the visibility predicate is dropped (admin sees all). When false, rows pass when public, owned by the viewer, or admitted by a cell_grant row.

type boolean

caller_actor_id?

Caller's actor_id for the actor-shaped grant branch. NULL = anonymous (actor-grants can never admit). Kept distinct from viewer_actor_id for the predicate's clarity (the visibility branch and the grant branch are independent concerns even when they currently agree).

type Uuid | null

caller_role_grant_roles?

Caller's role_grant roles, parallel-array projection of auth.role_grants (active-only — middleware filters). Pair-wise aligned with caller_role_grant_scope_ids. Empty array (or omitted) admits no role-shaped grants. The two arrays MUST have equal length — unnest(text[], uuid[]) null-pads on length mismatch and would silently widen role-grant admits.

type ReadonlyArray<string>

caller_role_grant_scope_ids?

Caller's role_grant scope ids, parallel-array projection. NULLs in the array mark global (any-scope) role_grants — IS NOT DISTINCT FROM handles them per design.

type ReadonlyArray<Uuid | null>

shared_with_caller_only?

When true, narrow to cells admitting the caller via a cell_grant row AND that the caller does not own. Authenticated only (viewer_actor_id must be set). Combine with kind / path_prefix etc. to scope further.

type boolean

order_by?

Sort column. Default created_at.

type 'created_at' | 'updated_at'

order_direction?

Sort direction. Default desc.

type 'asc' | 'desc'

limit?

Page size.

type number

offset?

Page offset.

type number

include_deleted?

Include soft-deleted rows. Default false.

type boolean

CellRow
#

db/cell_queries.ts view source

CellRow import type {CellRow} from '@fuzdev/fuz_app/db/cell_queries.js';

Row shape returned by cell SELECTs. data is typed as CellData — the storage layer trusts the wire validation; the row is what was written, and the wire validates CellData on every write.

Parent↔child membership and named relations live in the cell_item / cell_field sibling tables (see db/cell_item_queries.ts / db/cell_field_queries.ts). The cell row carries identity + content only.

grant_count is a derived projection (correlated subquery against cell_grant keyed by cell_id, served by idx_cell_grant_cell) — not a table column. New cells naturally land at 0.

id

type Uuid

data

type CellData

kind

type string | null

visibility

type CellVisibility

path

type string | null

refs

type Array<FactHash> | null

parent_id

type Uuid | null

root_id

type Uuid | null

moderation

type string | null

created_at

type Date

updated_at

type Date | null

deleted_at

type Date | null

created_by

type Uuid | null

updated_by

type Uuid | null

grant_count

type number

CellUpdatePatch
#

db/cell_queries.ts view source

CellUpdatePatch import type {CellUpdatePatch} from '@fuzdev/fuz_app/db/cell_queries.js';

Patch for query_cell_update. Fields left undefined are unchanged; path may be explicitly set to null to clear. refs is re-derived from data whenever data is updated.

data?

type Json

visibility?

type CellVisibility

path?

type string | null

updated_by?

type Uuid | null

query_cell_create
#

db/cell_queries.ts view source

(deps: QueryDeps, input: CellCreateQueryInput): Promise<CellRow> import {query_cell_create} from '@fuzdev/fuz_app/db/cell_queries.js';

Insert a cell row, deriving refs from data.

updated_by is left NULL on insert — same convention as updated_at (NULL until first update). The "last modifier" stamp is meaningful only after a real edit; copying the creator's id into updated_by at create time would make a no-op update by a different actor look authored by the creator.

deps

query deps

input

data, optional visibility, path, and ownership

returns

Promise<CellRow>

the inserted row

query_cell_delete
#

db/cell_queries.ts view source

(deps: QueryDeps, id: string & $brand<"Uuid">, options?: { deleted_by?: (string & $brand<"Uuid">) | null | undefined; } | undefined): Promise<boolean> import {query_cell_delete} from '@fuzdev/fuz_app/db/cell_queries.js';

Soft-delete a cell. Sets deleted_at = NOW(), updated_at = NOW(), and updated_by = options.deleted_by (or NULL). No-op when the row is already deleted.

deps

query deps

id

cell id

type string & $brand<"Uuid">

options?

deleted_by records who triggered the delete

type { deleted_by?: (string & $brand<"Uuid">) | null | undefined; } | undefined
optional

returns

Promise<boolean>

true when a row was soft-deleted, false when no active row matched

query_cell_get
#

db/cell_queries.ts view source

(deps: QueryDeps, id: string & $brand<"Uuid">, options?: { include_deleted?: boolean | undefined; } | undefined): Promise<CellRow | null> import {query_cell_get} from '@fuzdev/fuz_app/db/cell_queries.js';

Fetch a cell by id. Excludes soft-deleted rows by default.

deps

query deps

id

cell id

type string & $brand<"Uuid">

options?

include_deleted: true returns tombstones

type { include_deleted?: boolean | undefined; } | undefined
optional

returns

Promise<CellRow | null>

the row or null when not found (or soft-deleted and not requested)

query_cell_get_by_path
#

db/cell_queries.ts view source

(deps: QueryDeps, path: string): Promise<CellRow | null> import {query_cell_get_by_path} from '@fuzdev/fuz_app/db/cell_queries.js';

Fetch a cell by path. Excludes soft-deleted rows; the global partial unique index on path WHERE deleted_at IS NULL guarantees at most one result.

deps

query deps

path

the named lookup alias (e.g. /map/main)

type string

returns

Promise<CellRow | null>

the row or null when not found

query_cell_list
#

db/cell_queries.ts view source

(deps: QueryDeps, params: CellListParams): Promise<CellRow[]> import {query_cell_list} from '@fuzdev/fuz_app/db/cell_queries.js';

Filterable list query for the generic cell_list RPC.

Takes a flat filter shape (single optional clause per dimension; the cell_list API explicitly does NOT support OR'd alternatives within a dimension — keep it simple) plus an optional viewer-aware visibility predicate.

The visibility predicate mirrors can_view_cell in SQL form:

(viewer_is_admin OR cell.visibility = 'public' OR (viewer_actor_id IS NOT NULL AND created_by = viewer_actor_id) OR (viewer_actor_id IS NOT NULL AND <grant admits caller>))

The grants branch closes parity with can_view_cell: a SQL EXISTS over cell_grant, parameterized by the caller's actor_id and the parallel (role[], scope_id[]) projection of auth.role_grants. The caller's role_grants are materialized once via a caller_role_grants CTE so the role-grant unnest isn't re-scanned per outer row. Empty role_grant arrays are fine: the CTE yields zero rows, the inner EXISTS returns false, and the actor-grant branch still fires for actor-shaped grants.

shared_with_caller_only: true (shared_with: 'me' at the wire layer) takes a different SQL shape: instead of layering an extra conjunction on the cell-driven scan, it semi-joins through cell_grant, letting the planner drive from the (typically tiny) admitted-grant set via idx_cell_grant_actor / idx_cell_grant_role_scope rather than scanning every cell row. For a sharee with N grants over a table of M cells, the cost drops from O(M) to O(N + matched-cells). Owner-is-implicit (a cell's owner never appears as a grant principal) means the grants branch is itself owner-excluding, but the explicit created_by IS DISTINCT FROM caller guards against any future deviation. The shared_with branch does NOT bypass for admin: an admin asking "what's shared with me" wants their own grant footprint, not every cell.

Soft-deleted rows are excluded by default; opt-in via include_deleted.

deps

query deps

params

filter + visibility + ordering + pagination

returns

Promise<CellRow[]>

matching rows, ordered per order_by / order_direction

query_cell_list_by_creator
#

db/cell_queries.ts view source

(deps: QueryDeps, actor_id: string & $brand<"Uuid">, options?: Pick<CellListOptions, "limit" | "offset"> | undefined): Promise<...> import {query_cell_list_by_creator} from '@fuzdev/fuz_app/db/cell_queries.js';

List active cells created by an actor, newest first. Backed by the idx_cell_created_by partial index.

deps

query deps

actor_id

the creator's actor id

type string & $brand<"Uuid">

options?

pagination

type Pick<CellListOptions, "limit" | "offset"> | undefined
optional

returns

Promise<CellRow[]>

matching active rows

query_cell_list_by_kind
#

db/cell_queries.ts view source

(deps: QueryDeps, kind: string, options?: Pick<CellListOptions, "limit" | "offset"> | undefined): Promise<CellRow[]> import {query_cell_list_by_kind} from '@fuzdev/fuz_app/db/cell_queries.js';

List active cells with the given kind, newest first. Uses the idx_cell_kind index (cell.kind = ?).

deps

query deps

kind

cell.kind value to match (e.g. 'collection', 'entry')

type string

options?

pagination

type Pick<CellListOptions, "limit" | "offset"> | undefined
optional

returns

Promise<CellRow[]>

matching active rows

query_cell_load_many
#

db/cell_queries.ts view source

(deps: QueryDeps, ids: readonly (string & $brand<"Uuid">)[]): Promise<CellRow[]> import {query_cell_load_many} from '@fuzdev/fuz_app/db/cell_queries.js';

Bulk-load active cell rows by id, no visibility filter applied. Used by the strict relation-read filter (auth/cell_relation_visibility.ts's filter_visible_target_ids), which runs can_view_cell per row in memory rather than in SQL. Soft-deleted rows are excluded so relations to tombstones never surface.

deps

query deps

ids

cell ids to load (duplicates are harmless)

type readonly (string & $brand<"Uuid">)[]

returns

Promise<CellRow[]>

active rows in arbitrary order (caller indexes by id)

query_cell_set_moderation
#

db/cell_queries.ts view source

(deps: QueryDeps, id: string & $brand<"Uuid">, moderation: string, options: { set_visibility_public: boolean; updated_by?: (string & $brand<"Uuid">) | null | undefined; }): Promise<...> import {query_cell_set_moderation} from '@fuzdev/fuz_app/db/cell_queries.js';

Transition a contribution's moderation marker (the cell_moderate verb's write). On set_visibility_public (an approval) it also flips visibility to 'public' so the approved contribution goes live; a rejection leaves visibility untouched (stays private). updated_at / updated_by are stamped.

moderation is deliberately not writable through query_cell_update (it's absent from CellUpdatePatch) — gating the transition on a dedicated query (with the manage-tier authority check in the handler) is what stops an author self-approving their own pending cell.

deps

query deps

id

cell id

type string & $brand<"Uuid">

moderation

the terminal marker to write ('approved' | 'rejected')

type string

options

set_visibility_public flips visibility on approval; updated_by records the moderator

type { set_visibility_public: boolean; updated_by?: (string & $brand<"Uuid">) | null | undefined; }

returns

Promise<CellRow | null>

the updated row, or null when no active row matched (raced delete)

query_cell_update
#

db/cell_queries.ts view source

(deps: QueryDeps, id: string & $brand<"Uuid">, patch: CellUpdatePatch): Promise<CellRow | null> import {query_cell_update} from '@fuzdev/fuz_app/db/cell_queries.js';

Update a cell. Fields left undefined in the patch keep their existing value; explicit null writes NULL. refs is re-derived from data whenever the patch updates data. updated_at is bumped to NOW() on every successful update.

deps

query deps

id

cell id

type string & $brand<"Uuid">

patch

subset of mutable fields

returns

Promise<CellRow | null>

the updated row, or null when no row matched (already deleted or never existed)

Depends on
#

Imported by
#