@prometheus-ags/prometheus-entity-management 1.1.0 → 1.2.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.
package/dist/index.d.ts CHANGED
@@ -398,11 +398,25 @@ interface GraphTransaction {
398
398
  snapshot: () => GraphDataSnapshot;
399
399
  }
400
400
  interface GraphActionOptions<TInput, TResult> {
401
+ key?: string;
401
402
  optimistic?: (tx: GraphTransaction, input: TInput) => void;
402
403
  run: (tx: GraphTransaction, input: TInput) => Promise<TResult> | TResult;
403
404
  onSuccess?: (result: TResult, input: TInput, tx: GraphTransaction) => void;
404
405
  onError?: (error: Error, input: TInput) => void;
405
406
  }
407
+ interface GraphActionRecord$1 {
408
+ id: string;
409
+ key: string;
410
+ input: unknown;
411
+ enqueuedAt: string;
412
+ }
413
+ type GraphActionEvent = {
414
+ type: "enqueued";
415
+ record: GraphActionRecord$1;
416
+ } | {
417
+ type: "settled";
418
+ record: GraphActionRecord$1;
419
+ };
406
420
  declare function createGraphTransaction(): GraphTransaction;
407
421
  declare function createGraphAction<TInput, TResult>(opts: GraphActionOptions<TInput, TResult>): (input: TInput) => Promise<TResult>;
408
422
 
@@ -431,18 +445,96 @@ interface GraphEffectHandle {
431
445
  }
432
446
  declare function createGraphEffect<T>(opts: GraphEffectOptions<T>): GraphEffectHandle;
433
447
 
434
- interface GraphSnapshotExportOptions {
435
- scope: string;
436
- data: unknown;
437
- pretty?: boolean;
448
+ /**
449
+ * Compiles a {@link FilterSpec} into a Prisma `where` object (plain JSON-serializable shape).
450
+ *
451
+ * Operator mapping matches common Prisma filter APIs: `eq` → `equals`, string ops use `mode: "insensitive"`,
452
+ * `nin` → `notIn`, `arrayContains` → `has`, `isNull` uses `null` / `{ not: null }` per `value`, and `isNotNull` → `{ not: null }`.
453
+ * Unsupported ops (`between`, `arrayOverlaps`, `matches`, `custom`) are omitted from the result.
454
+ *
455
+ * Top-level clause arrays are combined with `AND`. {@link FilterGroup} uses `AND` / `OR` to match `group.logic`.
456
+ */
457
+ declare function toPrismaWhere(filter: FilterSpec): Record<string, unknown>;
458
+ /**
459
+ * Compiles a {@link SortSpec} into Prisma `orderBy` form: `[{ fieldName: "asc" | "desc" }, …]`.
460
+ * `nulls` and `comparator` on {@link SortClause} are ignored (local-only); extend callers if your Prisma version supports null ordering.
461
+ */
462
+ declare function toPrismaOrderBy(sort: SortSpec): Record<string, string>[];
463
+
464
+ /**
465
+ * Transport-agnostic comparison operators. Same spec can compile to REST, SQL, GraphQL, or local JS (`evaluator`).
466
+ * `custom` opts out of automatic serialization — use for predicates only the client can evaluate.
467
+ */
468
+ type FilterOperator = "eq" | "neq" | "gt" | "gte" | "lt" | "lte" | "in" | "nin" | "contains" | "startsWith" | "endsWith" | "isNull" | "isNotNull" | "between" | "arrayContains" | "arrayOverlaps" | "matches" | "custom";
469
+ /** Atomic filter: field path, operator, optional value, and optional JS predicate for `custom`. */
470
+ interface FilterClause {
471
+ field: string;
472
+ op: FilterOperator;
473
+ value?: unknown;
474
+ predicate?: (fieldValue: unknown, entity: Record<string, unknown>) => boolean;
438
475
  }
439
- interface GraphToolContext {
440
- store: ReturnType<typeof useGraphStore.getState>;
441
- queryOnce: typeof queryOnce;
442
- exportGraphSnapshot: typeof exportGraphSnapshot;
476
+ type FilterLogic = "and" | "or";
477
+ /** Nested boolean group so you can express `(A AND B) OR C` without losing structure when compiling to backends. */
478
+ interface FilterGroup {
479
+ logic: FilterLogic;
480
+ clauses: Array<FilterClause | FilterGroup>;
443
481
  }
444
- declare function exportGraphSnapshot(opts: GraphSnapshotExportOptions): string;
445
- declare function createGraphTool<TInput, TResult>(handler: (input: TInput, ctx: GraphToolContext) => Promise<TResult> | TResult): (input: TInput) => TResult | Promise<TResult>;
482
+ /** Top-level filter: flat AND list of clauses, or a recursive `FilterGroup`. */
483
+ type FilterSpec = FilterGroup | FilterClause[];
484
+ type SortDirection = "asc" | "desc";
485
+ /** Single sort key with optional null ordering and custom comparator for local sort parity with remote semantics. */
486
+ interface SortClause {
487
+ field: string;
488
+ direction: SortDirection;
489
+ nulls?: "first" | "last";
490
+ comparator?: (a: unknown, b: unknown) => number;
491
+ }
492
+ /** Ordered multi-key sort (stable application in `compareEntities`). */
493
+ type SortSpec = SortClause[];
494
+ /**
495
+ * Everything `useEntityView` needs to describe a virtualized collection: filters, sorts, and simple multi-field search.
496
+ * One descriptor can drive local evaluation, remote query compilation, or hybrid mode.
497
+ */
498
+ interface ViewDescriptor {
499
+ filter?: FilterSpec;
500
+ sort?: SortSpec;
501
+ search?: {
502
+ query: string;
503
+ fields: string[];
504
+ minChars?: number;
505
+ };
506
+ }
507
+ /**
508
+ * How complete local graph data is relative to the view: **local** (all in memory), **remote** (server must filter/sort), **hybrid** (show local fast + remote reconcile).
509
+ */
510
+ type CompletenessMode = "local" | "remote" | "hybrid";
511
+ /**
512
+ * Compile a view to flat REST query params (`sort`, `q`, and `field[op]=value` keys). Skips `custom` clauses — those cannot be expressed as strings.
513
+ */
514
+ declare function toRestParams(view: ViewDescriptor): Record<string, string>;
515
+ /**
516
+ * Compile a view to parameterized SQL fragments for server-side filtering/sorting. Unknown ops become `TRUE` — validate or restrict ops at the edge.
517
+ */
518
+ declare function toSQLClauses(view: ViewDescriptor): {
519
+ where: string;
520
+ orderBy: string;
521
+ params: unknown[];
522
+ };
523
+ /**
524
+ * Produce a GraphQL-variable-shaped object from a view (Hasura/Postgraphile-style `_op` maps). Intended as a starting point — wire to your actual schema.
525
+ */
526
+ declare function toGraphQLVariables(view: ViewDescriptor): {
527
+ where?: Record<string, unknown>;
528
+ orderBy?: Array<Record<string, unknown>>;
529
+ search?: string;
530
+ };
531
+
532
+ /**
533
+ * Normalize nested `FilterGroup` trees to a flat clause list for compilers that only understand atomic predicates.
534
+ */
535
+ declare function flattenClauses(filter: FilterSpec): FilterClause[];
536
+ /** True if any clause requires client-side `predicate` logic — forces local/hybrid evaluation paths that cannot be pushed to generic REST/SQL. */
537
+ declare function hasCustomPredicates(filter: FilterSpec): boolean;
446
538
 
447
539
  /**
448
540
  * Process-wide defaults for stale times, retries, and background revalidation.
@@ -549,360 +641,562 @@ declare function fetchEntity<TRaw, TEntity extends Record<string, unknown>>(opts
549
641
  declare function fetchList<TRaw, TEntity extends Record<string, unknown>>(opts: ListQueryOptions<TRaw, TEntity>, params: ListFetchParams, engineOpts: Required<EngineOptions>, isLoadMore?: boolean): Promise<void>;
550
642
 
551
643
  /**
552
- * Debug-time snapshot of entity graph health: counts, list queries, patches, staleness,
553
- * in-flight fetches, and engine subscriber ref-counts.
554
- *
555
- * Mount inside a DevTools panel or debug route; subscriber totals update via
556
- * `useSyncExternalStore` when hooks register/unregister interest, and graph fields
557
- * update through the Zustand store.
558
- */
559
- declare function useGraphDevTools(): {
560
- subscriberCount: number;
561
- entityCounts: Record<string, number>;
562
- totalEntities: number;
563
- listCount: number;
564
- patchedEntities: {
565
- type: string;
566
- id: string;
567
- }[];
568
- staleEntities: {
569
- type: string;
570
- id: string;
571
- }[];
572
- fetchingEntities: {
573
- type: string;
574
- id: string;
575
- }[];
576
- lists: {
577
- key: string;
578
- idCount: number;
579
- isFetching: boolean;
580
- isStale: boolean;
581
- }[];
582
- };
583
-
584
- /**
585
- * View-model for one entity row: merged canonical + patch data plus fetch lifecycle flags.
586
- * `isLoading` is true only when there is no data yet; `isFetching` includes background refreshes.
644
+ * Precompiled transport payloads for one view snapshot pass to REST, GraphQL, or SQL backends without re-deriving from `ViewDescriptor`.
587
645
  */
588
- interface UseEntityResult<T> {
589
- data: T | null;
590
- isLoading: boolean;
591
- isFetching: boolean;
592
- error: string | null;
593
- isStale: boolean;
594
- refetch: () => void;
646
+ interface ViewFetchParams {
647
+ rest: Record<string, string>;
648
+ graphql: ReturnType<typeof toGraphQLVariables>;
649
+ sql: ReturnType<typeof toSQLClauses>;
650
+ view: ViewDescriptor;
595
651
  }
596
652
  /**
597
- * Subscribe to a single normalized entity: populates the graph via `fetch`/`normalize`, dedupes in-flight work, and revalidates when stale.
598
- * Solves “query-owned silos” by keeping **one** canonical record every list/detail reads through.
599
- *
600
- * @param opts - Entity query instruction (`EntityQueryOptions`): `type`, `id`, `fetch`, `normalize`, optional `staleTime` / `enabled`
601
- * @returns Merged entity (`entities` + `patches`), loading/error/stale flags, and `refetch`
602
- *
603
- * @example
604
- * ```tsx
605
- * const { data, isLoading, refetch } = useEntity({
606
- * type: "Project",
607
- * id: projectId,
608
- * fetch: (id) => api.getProject(id),
609
- * normalize: (raw) => ({ ...raw, id: String(raw.id) }),
610
- * });
611
- * ```
653
+ * Configure a **live view** over a base list: filter/sort/search in JS when data is complete, or compile the same spec to remote params when not.
654
+ * `baseQueryKey` identifies the underlying id list in the graph; the hook may create additional keys for remote result sets.
612
655
  */
613
- declare function useEntity<TRaw, TEntity extends Record<string, unknown>>(opts: EntityQueryOptions<TRaw, TEntity>): UseEntityResult<TEntity>;
656
+ interface UseEntityViewOptions<TEntity extends Record<string, unknown>> {
657
+ type: EntityType;
658
+ baseQueryKey: unknown[];
659
+ view: ViewDescriptor;
660
+ mode?: CompletenessMode;
661
+ remoteFetch?: (params: ViewFetchParams) => Promise<ListResponse<TEntity>>;
662
+ normalize?: (raw: TEntity) => {
663
+ id: EntityId;
664
+ data: Record<string, unknown>;
665
+ };
666
+ remoteDebounce?: number;
667
+ staleTime?: number;
668
+ enabled?: boolean;
669
+ /** SSR-seeded ids written once into `lists[baseKey]` to avoid empty-state flash before hydration fetch. */
670
+ initialIds?: EntityId[];
671
+ /** SSR-seeded total for completeness heuristics when ids are preloaded. */
672
+ initialTotal?: number;
673
+ }
614
674
  /**
615
- * Resolved rows for a list query: **`items` joins `ids` to the graph** at render time so shared entities update everywhere.
675
+ * Rich list UI state: projected `items`/`viewIds`, completeness mode, remote vs local fetching flags, and imperative view updaters.
676
+ * `isShowingLocalPending` signals hybrid mode where stale local rows are visible while a remote round-trip runs.
616
677
  */
617
- interface UseEntityListResult<TEntity> {
678
+ interface UseEntityViewResult<TEntity> {
618
679
  items: TEntity[];
619
- ids: EntityId[];
680
+ viewIds: EntityId[];
681
+ viewTotal: number | null;
620
682
  isLoading: boolean;
621
683
  isFetching: boolean;
622
- isFetchingMore: boolean;
684
+ isRemoteFetching: boolean;
685
+ isShowingLocalPending: boolean;
623
686
  error: string | null;
624
687
  hasNextPage: boolean;
625
- hasPrevPage: boolean;
626
- total: number | null;
627
- currentPage: number | null;
628
688
  fetchNextPage: () => void;
689
+ isLocallyComplete: boolean;
690
+ completenessMode: CompletenessMode;
691
+ setView: (v: Partial<ViewDescriptor>) => void;
692
+ setFilter: (f: FilterSpec | null) => void;
693
+ setSort: (s: SortSpec | null) => void;
694
+ setSearch: (q: string) => void;
695
+ clearView: () => void;
629
696
  refetch: () => void;
697
+ isFetchingMore: boolean;
630
698
  }
631
699
  /**
632
- * Subscribe to a collection: stores **ordered ids** under a serialized `queryKey`, upserts each row into `entities`, and supports pagination.
633
- * Use when you need a table or feed backed by the shared graph (not an isolated query cache).
700
+ * Higher-level list hook: combines **graph-backed id lists**, declarative `ViewDescriptor`, local `applyView`, optional remote fetch, and realtime sorted insertion.
701
+ * Solves “filters tied to one query cache” by deriving the visible id order from the shared graph whenever possible.
634
702
  *
635
- * @param opts - List query instruction (`ListQueryOptions`)
636
- * @returns Hydrated `items`, raw `ids`, pagination helpers, and fetch flags
703
+ * @param opts - Base type/key, initial view, optional `remoteFetch` + `normalize`, SSR seeds, forced `mode`
704
+ * @returns Projected entities, view metadata, completeness, and setters for interactive toolbars
637
705
  *
638
706
  * @example
639
707
  * ```tsx
640
- * const { items, fetchNextPage, hasNextPage } = useEntityList({
708
+ * const view = useEntityView({
641
709
  * type: "Task",
642
- * queryKey: ["tasks", { projectId }],
643
- * fetch: (p) => api.listTasks({ ...p, projectId }),
710
+ * baseQueryKey: ["tasks", projectId],
711
+ * view: { filter: [{ field: "status", op: "eq", value: "open" }], sort: [{ field: "dueAt", direction: "asc" }] },
712
+ * remoteFetch: (p) => api.tasksQuery(p.rest),
644
713
  * normalize: (raw) => ({ id: raw.id, data: raw }),
645
- * mode: "append",
646
714
  * });
647
715
  * ```
648
716
  */
649
- declare function useEntityList<TRaw, TEntity extends Record<string, unknown>>(opts: ListQueryOptions<TRaw, TEntity>): UseEntityListResult<TEntity>;
717
+ declare function useEntityView<TEntity extends Record<string, unknown>>(opts: UseEntityViewOptions<TEntity>): UseEntityViewResult<TEntity>;
718
+
719
+ /** UI mode for a single CRUD surface: drives which panels/forms are active without scattering boolean flags. */
720
+ type CRUDMode = "list" | "detail" | "edit" | "create";
650
721
  /**
651
- * Options for graph-aware mutations: API call + optional normalization into `entities`, optimistic patches, and targeted invalidation.
652
- * Prefer `invalidateLists` prefixes / `invalidateEntities` over ad-hoc store calls so list UIs stay coherent.
722
+ * Wire one entity type into list+detail+forms: remote list via `useEntityView`, optional detail fetch, and create/update/delete callbacks.
723
+ * Mutations call `cascadeInvalidation` on success so related lists/entities refresh per registered schemas.
653
724
  */
654
- interface MutationOptions<TInput, TRaw, TEntity extends Record<string, unknown>> {
725
+ interface CRUDOptions<TEntity extends Record<string, unknown>> {
655
726
  type: EntityType;
656
- mutate: (input: TInput) => Promise<TRaw>;
657
- normalize?: (raw: TRaw, input: TInput) => {
727
+ listQueryKey: unknown[];
728
+ listFetch: (params: ViewFetchParams) => Promise<ListResponse<TEntity>>;
729
+ normalize: (raw: TEntity) => {
658
730
  id: EntityId;
659
731
  data: TEntity;
660
732
  };
661
- optimistic?: (input: TInput) => {
662
- id: EntityId;
663
- patch: Partial<TEntity>;
664
- } | null;
665
- invalidateLists?: string[];
666
- invalidateEntities?: Array<{
667
- type: EntityType;
668
- id: EntityId;
669
- }>;
670
- onSuccess?: (result: TRaw, input: TInput) => void;
671
- onError?: (error: Error, input: TInput) => void;
672
- }
673
- /** Imperative mutation handle plus explicit async `state` for UI pending/error/success. */
674
- interface UseMutationResult<TInput, TRaw> {
675
- mutate: (input: TInput) => Promise<TRaw | null>;
676
- trigger: (input: TInput) => void;
677
- reset: () => void;
678
- state: {
679
- isPending: boolean;
680
- isSuccess: boolean;
681
- isError: boolean;
682
- error: string | null;
683
- };
733
+ detailFetch?: (id: EntityId) => Promise<TEntity>;
734
+ onCreate?: (data: Partial<TEntity>) => Promise<TEntity>;
735
+ onUpdate?: (id: EntityId, patch: Partial<TEntity>) => Promise<TEntity>;
736
+ onDelete?: (id: EntityId) => Promise<void>;
737
+ createDefaults?: Partial<TEntity>;
738
+ initialView?: ViewDescriptor;
739
+ onCreateSuccess?: (entity: TEntity) => void;
740
+ onUpdateSuccess?: (entity: TEntity) => void;
741
+ onDeleteSuccess?: (id: EntityId) => void;
742
+ onError?: (op: "create" | "update" | "delete", error: Error) => void;
743
+ selectAfterCreate?: boolean;
744
+ clearSelectionAfterDelete?: boolean;
745
+ }
746
+ /** Public field input for CRUD setters: supports classic `keyof T` calls and dotted nested paths for JSON-backed forms. */
747
+ type EntityFieldPath<TEntity extends Record<string, unknown>> = keyof TEntity | string;
748
+ /** Tracks which fields diverge from loaded detail — edit buffer stays in React state so other views keep showing canonical graph data until save. */
749
+ interface DirtyFields<TEntity extends Record<string, unknown>> {
750
+ changed: ReadonlySet<EntityFieldPath<TEntity>>;
751
+ isDirty: boolean;
684
752
  }
685
753
  /**
686
- * Perform writes through your API while keeping the entity graph authoritative: optional optimistic `patchEntity`, commit via `upsertEntity`, rollback on failure.
687
- * Use when `useEntity`/`useEntityList` describe reads and you need a consistent mutation story without a second client cache.
688
- *
689
- * @example
690
- * ```tsx
691
- * const { mutate, state } = useEntityMutation({
692
- * type: "Task",
693
- * mutate: (input) => api.updateTask(input.id, input.patch),
694
- * normalize: (raw) => ({ id: raw.id, data: raw }),
695
- * });
696
- * ```
697
- */
698
- declare function useEntityMutation<TInput, TRaw, TEntity extends Record<string, unknown>>(opts: MutationOptions<TInput, TRaw, TEntity>): UseMutationResult<TInput, TRaw>;
699
- /**
700
- * Read/write **UI-only** fields for one entity (`patches` layer) so selection, hover, or transient state is visible in every view that reads that id.
701
- * Does not replace `useEntityCRUD`’s edit buffer for form drafts — patches are for shared, non-persisted overlays.
702
- *
703
- * @param type - Entity kind
704
- * @param id - Entity id (no-ops when null/undefined)
705
- * @returns Current patch slice and helpers `augment` / `unaugment` / `clear`
706
- */
707
- declare function useEntityAugment<TEntity extends Record<string, unknown>>(type: EntityType, id: EntityId | null | undefined): {
708
- patch: Partial<TEntity> | null;
709
- augment: (fields: Partial<TEntity>) => void;
710
- unaugment: (keys: (keyof TEntity)[]) => void;
711
- clear: () => void;
712
- };
713
- /**
714
- * Suspense-compatible version of `useEntity`. Throws a promise while the entity
715
- * is loading, allowing React Suspense boundaries to show fallback UI.
716
- * Once data is available, returns the entity data directly (never null).
717
- *
718
- * @param opts - Same as `useEntity` (`EntityQueryOptions`)
719
- * @returns `data` plus `isFetching`, `isStale`, and `refetch`
720
- * @throws Promise while loading (caught by the nearest Suspense boundary)
721
- * @throws Error if the fetch fails with no data, if `id` is missing when required, or if the entity never resolves
722
- */
723
- declare function useSuspenseEntity<TRaw, TEntity extends Record<string, unknown>>(opts: EntityQueryOptions<TRaw, TEntity>): {
724
- data: TEntity;
725
- isFetching: boolean;
726
- isStale: boolean;
727
- refetch: () => void;
728
- };
729
- /**
730
- * Suspense-compatible version of `useEntityList`. Throws a promise during
731
- * initial load, allowing Suspense boundaries to handle loading state.
732
- *
733
- * @param opts - Same as `useEntityList` (`ListQueryOptions`)
734
- * @returns Same shape as `useEntityList` except `isLoading` is omitted (always false when not suspended)
735
- * @throws Promise while initially loading (caught by the nearest Suspense boundary)
736
- * @throws Error if the fetch fails while the list is still empty
754
+ * Everything a CRUD screen needs: composed `list` view, selection, detail subscription, relation joins, edit/create buffers, and mutating actions.
755
+ * `applyOptimistic` is the escape hatch to mirror the buffer into `patches` for instant sliders/toggles without committing `save` yet.
737
756
  */
738
- declare function useSuspenseEntityList<TRaw, TEntity extends Record<string, unknown>>(opts: ListQueryOptions<TRaw, TEntity>): Omit<UseEntityListResult<TEntity>, "isLoading">;
739
-
757
+ interface CRUDState<TEntity extends Record<string, unknown>> {
758
+ mode: CRUDMode;
759
+ setMode: (mode: CRUDMode) => void;
760
+ list: UseEntityViewResult<TEntity>;
761
+ selectedId: EntityId | null;
762
+ select: (id: EntityId | null) => void;
763
+ openDetail: (id: EntityId) => void;
764
+ detail: TEntity | null;
765
+ detailIsLoading: boolean;
766
+ detailError: string | null;
767
+ relations: Record<string, unknown>;
768
+ editBuffer: Partial<TEntity>;
769
+ setField: (field: EntityFieldPath<TEntity>, value: unknown) => void;
770
+ setFields: (fields: Partial<TEntity>) => void;
771
+ resetBuffer: () => void;
772
+ dirty: DirtyFields<TEntity>;
773
+ startEdit: (id?: EntityId) => void;
774
+ cancelEdit: () => void;
775
+ save: () => Promise<TEntity | null>;
776
+ isSaving: boolean;
777
+ saveError: string | null;
778
+ applyOptimistic: () => void;
779
+ createBuffer: Partial<TEntity>;
780
+ setCreateField: (field: EntityFieldPath<TEntity>, value: unknown) => void;
781
+ setCreateFields: (fields: Partial<TEntity>) => void;
782
+ resetCreateBuffer: () => void;
783
+ startCreate: () => void;
784
+ cancelCreate: () => void;
785
+ create: () => Promise<TEntity | null>;
786
+ isCreating: boolean;
787
+ createError: string | null;
788
+ deleteEntity: (id?: EntityId) => Promise<void>;
789
+ isDeleting: boolean;
790
+ deleteError: string | null;
791
+ isEditing: boolean;
792
+ }
740
793
  /**
741
- * Compiles a {@link FilterSpec} into a Prisma `where` object (plain JSON-serializable shape).
742
- *
743
- * Operator mapping matches common Prisma filter APIs: `eq` → `equals`, string ops use `mode: "insensitive"`,
744
- * `nin` → `notIn`, `arrayContains` → `has`, `isNull` uses `null` / `{ not: null }` per `value`, and `isNotNull` → `{ not: null }`.
745
- * Unsupported ops (`between`, `arrayOverlaps`, `matches`, `custom`) are omitted from the result.
794
+ * Batteries-included CRUD orchestration over the entity graph: list filtering/sorting, detail fetch, isolated edit buffer, optimistic create row, and transactional save/delete with rollback.
795
+ * Prefer this over ad-hoc `useEntity` wiring when building admin-style tables + side panels + forms for one resource.
746
796
  *
747
- * Top-level clause arrays are combined with `AND`. {@link FilterGroup} uses `AND` / `OR` to match `group.logic`.
748
- */
749
- declare function toPrismaWhere(filter: FilterSpec): Record<string, unknown>;
750
- /**
751
- * Compiles a {@link SortSpec} into Prisma `orderBy` form: `[{ fieldName: "asc" | "desc" }, …]`.
752
- * `nulls` and `comparator` on {@link SortClause} are ignored (local-only); extend callers if your Prisma version supports null ordering.
797
+ * @param opts - `CRUDOptions` for type, list key/fetch, normalization, lifecycle callbacks
798
+ * @returns `CRUDState` with list/detail/edit/create controls
753
799
  */
754
- declare function toPrismaOrderBy(sort: SortSpec): Record<string, string>[];
800
+ declare function useEntityCRUD<TEntity extends Record<string, unknown>>(opts: CRUDOptions<TEntity>): CRUDState<TEntity>;
755
801
 
756
- /**
757
- * Transport-agnostic comparison operators. Same spec can compile to REST, SQL, GraphQL, or local JS (`evaluator`).
758
- * `custom` opts out of automatic serialization — use for predicates only the client can evaluate.
759
- */
760
- type FilterOperator = "eq" | "neq" | "gt" | "gte" | "lt" | "lte" | "in" | "nin" | "contains" | "startsWith" | "endsWith" | "isNull" | "isNotNull" | "between" | "arrayContains" | "arrayOverlaps" | "matches" | "custom";
761
- /** Atomic filter: field path, operator, optional value, and optional JS predicate for `custom`. */
762
- interface FilterClause {
802
+ type FieldType = "text" | "textarea" | "number" | "email" | "url" | "date" | "boolean" | "enum" | "json" | "markdown" | "custom";
803
+ interface FieldDescriptor<TEntity> {
763
804
  field: string;
764
- op: FilterOperator;
765
- value?: unknown;
766
- predicate?: (fieldValue: unknown, entity: Record<string, unknown>) => boolean;
805
+ label: string;
806
+ type: FieldType;
807
+ required?: boolean;
808
+ placeholder?: string;
809
+ options?: Array<{
810
+ value: string;
811
+ label: string;
812
+ }>;
813
+ hint?: string;
814
+ render?: (value: unknown, entity: TEntity) => React$1.ReactNode;
815
+ editControl?: (value: unknown, onChange: (v: unknown) => void, entity: Partial<TEntity>) => React$1.ReactNode;
816
+ hideOnCreate?: boolean;
817
+ hideOnEdit?: boolean;
818
+ readonlyOnEdit?: boolean;
767
819
  }
768
- type FilterLogic = "and" | "or";
769
- /** Nested boolean group so you can express `(A AND B) OR C` without losing structure when compiling to backends. */
770
- interface FilterGroup {
771
- logic: FilterLogic;
772
- clauses: Array<FilterClause | FilterGroup>;
820
+ declare function Sheet({ open, onClose, title, subtitle, children, footer, width }: {
821
+ open: boolean;
822
+ onClose: () => void;
823
+ title: string;
824
+ subtitle?: string;
825
+ children: React$1.ReactNode;
826
+ footer?: React$1.ReactNode;
827
+ width?: string;
828
+ }): react_jsx_runtime.JSX.Element;
829
+ declare function EntityDetailSheet<TEntity extends Record<string, unknown>>({ crud, fields, title, description, children, showEditButton, showDeleteButton, deleteConfirmMessage }: {
830
+ crud: CRUDState<TEntity>;
831
+ fields: FieldDescriptor<TEntity>[];
832
+ title?: string | ((e: TEntity) => string);
833
+ description?: string | ((e: TEntity) => string);
834
+ children?: (entity: TEntity, crud: CRUDState<TEntity>) => React$1.ReactNode;
835
+ showEditButton?: boolean;
836
+ showDeleteButton?: boolean;
837
+ deleteConfirmMessage?: string;
838
+ }): react_jsx_runtime.JSX.Element;
839
+ declare function EntityFormSheet<TEntity extends Record<string, unknown>>({ crud, fields, createTitle, editTitle }: {
840
+ crud: CRUDState<TEntity>;
841
+ fields: FieldDescriptor<TEntity>[];
842
+ createTitle?: string;
843
+ editTitle?: string | ((e: TEntity) => string);
844
+ }): react_jsx_runtime.JSX.Element;
845
+
846
+ interface JsonSchemaObject {
847
+ $id?: string;
848
+ title?: string;
849
+ description?: string;
850
+ type?: string | string[];
851
+ format?: string;
852
+ enum?: unknown[];
853
+ default?: unknown;
854
+ properties?: Record<string, JsonSchemaObject>;
855
+ items?: JsonSchemaObject;
856
+ required?: string[];
857
+ ["x-a2ui-component"]?: string;
858
+ ["x-display-order"]?: number;
859
+ ["x-field-type"]?: string;
860
+ ["x-hidden"]?: boolean;
861
+ }
862
+ interface EntityJsonSchemaConfig {
863
+ entityType: string;
864
+ schemaId?: string;
865
+ field?: string;
866
+ version?: string;
867
+ source?: "static" | "runtime" | "ai";
868
+ schema: JsonSchemaObject;
869
+ }
870
+ interface GetEntityJsonSchemaOptions {
871
+ entityType: string;
872
+ schemaId?: string;
873
+ field?: string;
874
+ }
875
+ interface SchemaFieldDescriptor<TEntity extends Record<string, unknown> = Record<string, unknown>> extends FieldDescriptor<TEntity> {
876
+ schemaPath: string;
877
+ schema: JsonSchemaObject;
878
+ componentHint?: string;
879
+ }
880
+ interface BuildEntityFieldsFromSchemaOptions {
881
+ schema: JsonSchemaObject;
882
+ rootField?: string;
883
+ }
884
+ interface GraphSnapshotWithSchemasOptions {
885
+ scope: string;
886
+ data: unknown;
887
+ schemas: Array<EntityJsonSchemaConfig | null | undefined>;
888
+ pretty?: boolean;
773
889
  }
774
- /** Top-level filter: flat AND list of clauses, or a recursive `FilterGroup`. */
775
- type FilterSpec = FilterGroup | FilterClause[];
776
- type SortDirection = "asc" | "desc";
777
- /** Single sort key with optional null ordering and custom comparator for local sort parity with remote semantics. */
778
- interface SortClause {
779
- field: string;
780
- direction: SortDirection;
781
- nulls?: "first" | "last";
782
- comparator?: (a: unknown, b: unknown) => number;
890
+ declare function registerEntityJsonSchema(config: EntityJsonSchemaConfig): void;
891
+ declare function registerRuntimeSchema(config: EntityJsonSchemaConfig): void;
892
+ declare function getEntityJsonSchema(opts: GetEntityJsonSchemaOptions): EntityJsonSchemaConfig | null;
893
+ declare function useSchemaEntityFields<TEntity extends Record<string, unknown> = Record<string, unknown>>(opts: GetEntityJsonSchemaOptions & {
894
+ schema?: JsonSchemaObject;
895
+ rootField?: string;
896
+ }): SchemaFieldDescriptor<TEntity>[];
897
+ declare function buildEntityFieldsFromSchema<TEntity extends Record<string, unknown> = Record<string, unknown>>(opts: BuildEntityFieldsFromSchemaOptions): SchemaFieldDescriptor<TEntity>[];
898
+ declare function exportGraphSnapshotWithSchemas(opts: GraphSnapshotWithSchemasOptions): string;
899
+ declare function renderMarkdownToHtml(value: string): string;
900
+ declare function MarkdownFieldRenderer({ value, className }: {
901
+ value: string;
902
+ className?: string;
903
+ }): react_jsx_runtime.JSX.Element;
904
+ declare function MarkdownFieldEditor({ value, onChange, placeholder, }: {
905
+ value: string;
906
+ onChange: (value: string) => void;
907
+ placeholder?: string;
908
+ }): react_jsx_runtime.JSX.Element;
909
+
910
+ interface GraphSnapshotExportOptions {
911
+ scope: string;
912
+ data: unknown;
913
+ pretty?: boolean;
783
914
  }
784
- /** Ordered multi-key sort (stable application in `compareEntities`). */
785
- type SortSpec = SortClause[];
786
- /**
787
- * Everything `useEntityView` needs to describe a virtualized collection: filters, sorts, and simple multi-field search.
788
- * One descriptor can drive local evaluation, remote query compilation, or hybrid mode.
789
- */
790
- interface ViewDescriptor {
791
- filter?: FilterSpec;
792
- sort?: SortSpec;
793
- search?: {
794
- query: string;
795
- fields: string[];
796
- minChars?: number;
915
+ interface GraphToolContext {
916
+ store: ReturnType<typeof useGraphStore.getState>;
917
+ queryOnce: typeof queryOnce;
918
+ exportGraphSnapshot: typeof exportGraphSnapshot;
919
+ }
920
+ interface SchemaGraphToolContext extends GraphToolContext {
921
+ getEntityJsonSchema: typeof getEntityJsonSchema;
922
+ exportGraphSnapshotWithSchemas: typeof exportGraphSnapshotWithSchemas;
923
+ }
924
+ declare function exportGraphSnapshot(opts: GraphSnapshotExportOptions): string;
925
+ declare function createGraphTool<TInput, TResult>(handler: (input: TInput, ctx: GraphToolContext) => Promise<TResult> | TResult): (input: TInput) => TResult | Promise<TResult>;
926
+ declare function createSchemaGraphTool<TInput, TResult>(handler: (input: TInput, ctx: SchemaGraphToolContext) => Promise<TResult> | TResult): (input: TInput) => TResult | Promise<TResult>;
927
+
928
+ interface GraphPersistenceAdapter {
929
+ get: (key: string) => Promise<string | null> | string | null;
930
+ set: (key: string, value: string) => Promise<void> | void;
931
+ remove?: (key: string) => Promise<void> | void;
932
+ }
933
+ interface GraphActionRecord {
934
+ id: string;
935
+ key: string;
936
+ input: unknown;
937
+ enqueuedAt: string;
938
+ }
939
+ interface GraphSyncStatus {
940
+ phase: "idle" | "hydrating" | "syncing" | "ready" | "offline" | "error";
941
+ isOnline: boolean;
942
+ isSynced: boolean;
943
+ pendingActions: number;
944
+ lastHydratedAt: string | null;
945
+ lastPersistedAt: string | null;
946
+ storageKey: string | null;
947
+ error: string | null;
948
+ }
949
+ interface GraphSnapshotPayload {
950
+ version: 1;
951
+ snapshot: {
952
+ entities: ReturnType<typeof useGraphStore.getState>["entities"];
953
+ patches: ReturnType<typeof useGraphStore.getState>["patches"];
954
+ entityStates: ReturnType<typeof useGraphStore.getState>["entityStates"];
955
+ syncMetadata: ReturnType<typeof useGraphStore.getState>["syncMetadata"];
956
+ lists: ReturnType<typeof useGraphStore.getState>["lists"];
797
957
  };
958
+ pendingActions: GraphActionRecord[];
798
959
  }
799
- /**
800
- * How complete local graph data is relative to the view: **local** (all in memory), **remote** (server must filter/sort), **hybrid** (show local fast + remote reconcile).
801
- */
802
- type CompletenessMode = "local" | "remote" | "hybrid";
803
- /**
804
- * Compile a view to flat REST query params (`sort`, `q`, and `field[op]=value` keys). Skips `custom` clauses — those cannot be expressed as strings.
805
- */
806
- declare function toRestParams(view: ViewDescriptor): Record<string, string>;
807
- /**
808
- * Compile a view to parameterized SQL fragments for server-side filtering/sorting. Unknown ops become `TRUE` — validate or restrict ops at the edge.
809
- */
810
- declare function toSQLClauses(view: ViewDescriptor): {
811
- where: string;
812
- orderBy: string;
813
- params: unknown[];
814
- };
815
- /**
816
- * Produce a GraphQL-variable-shaped object from a view (Hasura/Postgraphile-style `_op` maps). Intended as a starting point — wire to your actual schema.
817
- */
818
- declare function toGraphQLVariables(view: ViewDescriptor): {
819
- where?: Record<string, unknown>;
820
- orderBy?: Array<Record<string, unknown>>;
821
- search?: string;
822
- };
960
+ interface PersistGraphToStorageOptions {
961
+ storage: GraphPersistenceAdapter;
962
+ key: string;
963
+ pendingActions?: GraphActionRecord[];
964
+ }
965
+ interface HydrateGraphFromStorageOptions {
966
+ storage: GraphPersistenceAdapter;
967
+ key: string;
968
+ }
969
+ interface StartLocalFirstGraphOptions {
970
+ storage: GraphPersistenceAdapter;
971
+ key?: string;
972
+ replayPendingActions?: boolean;
973
+ onlineSource?: {
974
+ getIsOnline: () => boolean;
975
+ subscribe: (listener: (online: boolean) => void) => () => void;
976
+ };
977
+ persistDebounceMs?: number;
978
+ }
979
+ interface LocalFirstGraphRuntime {
980
+ ready: Promise<void>;
981
+ dispose: () => void;
982
+ persistNow: () => Promise<void>;
983
+ hydrate: () => Promise<Awaited<ReturnType<typeof hydrateGraphFromStorage>>>;
984
+ getStatus: () => GraphSyncStatus;
985
+ }
986
+ declare function useGraphSyncStatus(): GraphSyncStatus;
987
+ declare function persistGraphToStorage(opts: PersistGraphToStorageOptions): Promise<{
988
+ ok: true;
989
+ key: string;
990
+ bytes: number;
991
+ persistedAt: string;
992
+ }>;
993
+ declare function hydrateGraphFromStorage(opts: HydrateGraphFromStorageOptions): Promise<{
994
+ ok: false;
995
+ key: string;
996
+ hydratedAt: null;
997
+ entityCounts: {};
998
+ error: string;
999
+ pendingActions?: undefined;
1000
+ } | {
1001
+ ok: true;
1002
+ key: string;
1003
+ hydratedAt: string;
1004
+ entityCounts: {
1005
+ [k: string]: number;
1006
+ };
1007
+ pendingActions: GraphActionRecord[];
1008
+ error?: undefined;
1009
+ }>;
1010
+ declare function startLocalFirstGraph(opts: StartLocalFirstGraphOptions): LocalFirstGraphRuntime;
823
1011
 
824
1012
  /**
825
- * Normalize nested `FilterGroup` trees to a flat clause list for compilers that only understand atomic predicates.
1013
+ * Debug-time snapshot of entity graph health: counts, list queries, patches, staleness,
1014
+ * in-flight fetches, and engine subscriber ref-counts.
1015
+ *
1016
+ * Mount inside a DevTools panel or debug route; subscriber totals update via
1017
+ * `useSyncExternalStore` when hooks register/unregister interest, and graph fields
1018
+ * update through the Zustand store.
826
1019
  */
827
- declare function flattenClauses(filter: FilterSpec): FilterClause[];
828
- /** True if any clause requires client-side `predicate` logic — forces local/hybrid evaluation paths that cannot be pushed to generic REST/SQL. */
829
- declare function hasCustomPredicates(filter: FilterSpec): boolean;
1020
+ declare function useGraphDevTools(): {
1021
+ subscriberCount: number;
1022
+ entityCounts: Record<string, number>;
1023
+ totalEntities: number;
1024
+ listCount: number;
1025
+ patchedEntities: {
1026
+ type: string;
1027
+ id: string;
1028
+ }[];
1029
+ staleEntities: {
1030
+ type: string;
1031
+ id: string;
1032
+ }[];
1033
+ fetchingEntities: {
1034
+ type: string;
1035
+ id: string;
1036
+ }[];
1037
+ lists: {
1038
+ key: string;
1039
+ idCount: number;
1040
+ isFetching: boolean;
1041
+ isStale: boolean;
1042
+ }[];
1043
+ };
830
1044
 
831
1045
  /**
832
- * Precompiled transport payloads for one view snapshot pass to REST, GraphQL, or SQL backends without re-deriving from `ViewDescriptor`.
1046
+ * View-model for one entity row: merged canonical + patch data plus fetch lifecycle flags.
1047
+ * `isLoading` is true only when there is no data yet; `isFetching` includes background refreshes.
833
1048
  */
834
- interface ViewFetchParams {
835
- rest: Record<string, string>;
836
- graphql: ReturnType<typeof toGraphQLVariables>;
837
- sql: ReturnType<typeof toSQLClauses>;
838
- view: ViewDescriptor;
1049
+ interface UseEntityResult<T> {
1050
+ data: T | null;
1051
+ isLoading: boolean;
1052
+ isFetching: boolean;
1053
+ error: string | null;
1054
+ isStale: boolean;
1055
+ refetch: () => void;
839
1056
  }
840
1057
  /**
841
- * Configure a **live view** over a base list: filter/sort/search in JS when data is complete, or compile the same spec to remote params when not.
842
- * `baseQueryKey` identifies the underlying id list in the graph; the hook may create additional keys for remote result sets.
843
- */
844
- interface UseEntityViewOptions<TEntity extends Record<string, unknown>> {
845
- type: EntityType;
846
- baseQueryKey: unknown[];
847
- view: ViewDescriptor;
848
- mode?: CompletenessMode;
849
- remoteFetch?: (params: ViewFetchParams) => Promise<ListResponse<TEntity>>;
850
- normalize?: (raw: TEntity) => {
851
- id: EntityId;
852
- data: Record<string, unknown>;
853
- };
854
- remoteDebounce?: number;
855
- staleTime?: number;
856
- enabled?: boolean;
857
- /** SSR-seeded ids written once into `lists[baseKey]` to avoid empty-state flash before hydration fetch. */
858
- initialIds?: EntityId[];
859
- /** SSR-seeded total for completeness heuristics when ids are preloaded. */
860
- initialTotal?: number;
861
- }
1058
+ * Subscribe to a single normalized entity: populates the graph via `fetch`/`normalize`, dedupes in-flight work, and revalidates when stale.
1059
+ * Solves “query-owned silos” by keeping **one** canonical record every list/detail reads through.
1060
+ *
1061
+ * @param opts - Entity query instruction (`EntityQueryOptions`): `type`, `id`, `fetch`, `normalize`, optional `staleTime` / `enabled`
1062
+ * @returns Merged entity (`entities` + `patches`), loading/error/stale flags, and `refetch`
1063
+ *
1064
+ * @example
1065
+ * ```tsx
1066
+ * const { data, isLoading, refetch } = useEntity({
1067
+ * type: "Project",
1068
+ * id: projectId,
1069
+ * fetch: (id) => api.getProject(id),
1070
+ * normalize: (raw) => ({ ...raw, id: String(raw.id) }),
1071
+ * });
1072
+ * ```
1073
+ */
1074
+ declare function useEntity<TRaw, TEntity extends Record<string, unknown>>(opts: EntityQueryOptions<TRaw, TEntity>): UseEntityResult<TEntity>;
862
1075
  /**
863
- * Rich list UI state: projected `items`/`viewIds`, completeness mode, remote vs local fetching flags, and imperative view updaters.
864
- * `isShowingLocalPending` signals hybrid mode where stale local rows are visible while a remote round-trip runs.
1076
+ * Resolved rows for a list query: **`items` joins `ids` to the graph** at render time so shared entities update everywhere.
865
1077
  */
866
- interface UseEntityViewResult<TEntity> {
1078
+ interface UseEntityListResult<TEntity> {
867
1079
  items: TEntity[];
868
- viewIds: EntityId[];
869
- viewTotal: number | null;
1080
+ ids: EntityId[];
870
1081
  isLoading: boolean;
871
1082
  isFetching: boolean;
872
- isRemoteFetching: boolean;
873
- isShowingLocalPending: boolean;
1083
+ isFetchingMore: boolean;
874
1084
  error: string | null;
875
1085
  hasNextPage: boolean;
1086
+ hasPrevPage: boolean;
1087
+ total: number | null;
1088
+ currentPage: number | null;
876
1089
  fetchNextPage: () => void;
877
- isLocallyComplete: boolean;
878
- completenessMode: CompletenessMode;
879
- setView: (v: Partial<ViewDescriptor>) => void;
880
- setFilter: (f: FilterSpec | null) => void;
881
- setSort: (s: SortSpec | null) => void;
882
- setSearch: (q: string) => void;
883
- clearView: () => void;
884
1090
  refetch: () => void;
885
- isFetchingMore: boolean;
886
1091
  }
887
1092
  /**
888
- * Higher-level list hook: combines **graph-backed id lists**, declarative `ViewDescriptor`, local `applyView`, optional remote fetch, and realtime sorted insertion.
889
- * Solves “filters tied to one query cache” by deriving the visible id order from the shared graph whenever possible.
1093
+ * Subscribe to a collection: stores **ordered ids** under a serialized `queryKey`, upserts each row into `entities`, and supports pagination.
1094
+ * Use when you need a table or feed backed by the shared graph (not an isolated query cache).
890
1095
  *
891
- * @param opts - Base type/key, initial view, optional `remoteFetch` + `normalize`, SSR seeds, forced `mode`
892
- * @returns Projected entities, view metadata, completeness, and setters for interactive toolbars
1096
+ * @param opts - List query instruction (`ListQueryOptions`)
1097
+ * @returns Hydrated `items`, raw `ids`, pagination helpers, and fetch flags
893
1098
  *
894
1099
  * @example
895
1100
  * ```tsx
896
- * const view = useEntityView({
1101
+ * const { items, fetchNextPage, hasNextPage } = useEntityList({
897
1102
  * type: "Task",
898
- * baseQueryKey: ["tasks", projectId],
899
- * view: { filter: [{ field: "status", op: "eq", value: "open" }], sort: [{ field: "dueAt", direction: "asc" }] },
900
- * remoteFetch: (p) => api.tasksQuery(p.rest),
1103
+ * queryKey: ["tasks", { projectId }],
1104
+ * fetch: (p) => api.listTasks({ ...p, projectId }),
901
1105
  * normalize: (raw) => ({ id: raw.id, data: raw }),
1106
+ * mode: "append",
902
1107
  * });
903
1108
  * ```
904
1109
  */
905
- declare function useEntityView<TEntity extends Record<string, unknown>>(opts: UseEntityViewOptions<TEntity>): UseEntityViewResult<TEntity>;
1110
+ declare function useEntityList<TRaw, TEntity extends Record<string, unknown>>(opts: ListQueryOptions<TRaw, TEntity>): UseEntityListResult<TEntity>;
1111
+ /**
1112
+ * Options for graph-aware mutations: API call + optional normalization into `entities`, optimistic patches, and targeted invalidation.
1113
+ * Prefer `invalidateLists` prefixes / `invalidateEntities` over ad-hoc store calls so list UIs stay coherent.
1114
+ */
1115
+ interface MutationOptions<TInput, TRaw, TEntity extends Record<string, unknown>> {
1116
+ type: EntityType;
1117
+ mutate: (input: TInput) => Promise<TRaw>;
1118
+ normalize?: (raw: TRaw, input: TInput) => {
1119
+ id: EntityId;
1120
+ data: TEntity;
1121
+ };
1122
+ optimistic?: (input: TInput) => {
1123
+ id: EntityId;
1124
+ patch: Partial<TEntity>;
1125
+ } | null;
1126
+ invalidateLists?: string[];
1127
+ invalidateEntities?: Array<{
1128
+ type: EntityType;
1129
+ id: EntityId;
1130
+ }>;
1131
+ onSuccess?: (result: TRaw, input: TInput) => void;
1132
+ onError?: (error: Error, input: TInput) => void;
1133
+ }
1134
+ /** Imperative mutation handle plus explicit async `state` for UI pending/error/success. */
1135
+ interface UseMutationResult<TInput, TRaw> {
1136
+ mutate: (input: TInput) => Promise<TRaw | null>;
1137
+ trigger: (input: TInput) => void;
1138
+ reset: () => void;
1139
+ state: {
1140
+ isPending: boolean;
1141
+ isSuccess: boolean;
1142
+ isError: boolean;
1143
+ error: string | null;
1144
+ };
1145
+ }
1146
+ /**
1147
+ * Perform writes through your API while keeping the entity graph authoritative: optional optimistic `patchEntity`, commit via `upsertEntity`, rollback on failure.
1148
+ * Use when `useEntity`/`useEntityList` describe reads and you need a consistent mutation story without a second client cache.
1149
+ *
1150
+ * @example
1151
+ * ```tsx
1152
+ * const { mutate, state } = useEntityMutation({
1153
+ * type: "Task",
1154
+ * mutate: (input) => api.updateTask(input.id, input.patch),
1155
+ * normalize: (raw) => ({ id: raw.id, data: raw }),
1156
+ * });
1157
+ * ```
1158
+ */
1159
+ declare function useEntityMutation<TInput, TRaw, TEntity extends Record<string, unknown>>(opts: MutationOptions<TInput, TRaw, TEntity>): UseMutationResult<TInput, TRaw>;
1160
+ /**
1161
+ * Read/write **UI-only** fields for one entity (`patches` layer) so selection, hover, or transient state is visible in every view that reads that id.
1162
+ * Does not replace `useEntityCRUD`’s edit buffer for form drafts — patches are for shared, non-persisted overlays.
1163
+ *
1164
+ * @param type - Entity kind
1165
+ * @param id - Entity id (no-ops when null/undefined)
1166
+ * @returns Current patch slice and helpers `augment` / `unaugment` / `clear`
1167
+ */
1168
+ declare function useEntityAugment<TEntity extends Record<string, unknown>>(type: EntityType, id: EntityId | null | undefined): {
1169
+ patch: Partial<TEntity> | null;
1170
+ augment: (fields: Partial<TEntity>) => void;
1171
+ unaugment: (keys: (keyof TEntity)[]) => void;
1172
+ clear: () => void;
1173
+ };
1174
+ /**
1175
+ * Suspense-compatible version of `useEntity`. Throws a promise while the entity
1176
+ * is loading, allowing React Suspense boundaries to show fallback UI.
1177
+ * Once data is available, returns the entity data directly (never null).
1178
+ *
1179
+ * @param opts - Same as `useEntity` (`EntityQueryOptions`)
1180
+ * @returns `data` plus `isFetching`, `isStale`, and `refetch`
1181
+ * @throws Promise while loading (caught by the nearest Suspense boundary)
1182
+ * @throws Error if the fetch fails with no data, if `id` is missing when required, or if the entity never resolves
1183
+ */
1184
+ declare function useSuspenseEntity<TRaw, TEntity extends Record<string, unknown>>(opts: EntityQueryOptions<TRaw, TEntity>): {
1185
+ data: TEntity;
1186
+ isFetching: boolean;
1187
+ isStale: boolean;
1188
+ refetch: () => void;
1189
+ };
1190
+ /**
1191
+ * Suspense-compatible version of `useEntityList`. Throws a promise during
1192
+ * initial load, allowing Suspense boundaries to handle loading state.
1193
+ *
1194
+ * @param opts - Same as `useEntityList` (`ListQueryOptions`)
1195
+ * @returns Same shape as `useEntityList` except `isLoading` is omitted (always false when not suspended)
1196
+ * @throws Promise while initially loading (caught by the nearest Suspense boundary)
1197
+ * @throws Error if the fetch fails while the list is still empty
1198
+ */
1199
+ declare function useSuspenseEntityList<TRaw, TEntity extends Record<string, unknown>>(opts: ListQueryOptions<TRaw, TEntity>): Omit<UseEntityListResult<TEntity>, "isLoading">;
906
1200
 
907
1201
  /**
908
1202
  * Evaluate `FilterSpec` against one in-memory entity — mirrors remote semantics as closely as plain JS allows.
@@ -936,87 +1230,6 @@ declare function checkCompleteness(loadedCount: number, total: number | null, ha
936
1230
  reason: string;
937
1231
  };
938
1232
 
939
- /** UI mode for a single CRUD surface: drives which panels/forms are active without scattering boolean flags. */
940
- type CRUDMode = "list" | "detail" | "edit" | "create";
941
- /**
942
- * Wire one entity type into list+detail+forms: remote list via `useEntityView`, optional detail fetch, and create/update/delete callbacks.
943
- * Mutations call `cascadeInvalidation` on success so related lists/entities refresh per registered schemas.
944
- */
945
- interface CRUDOptions<TEntity extends Record<string, unknown>> {
946
- type: EntityType;
947
- listQueryKey: unknown[];
948
- listFetch: (params: ViewFetchParams) => Promise<ListResponse<TEntity>>;
949
- normalize: (raw: TEntity) => {
950
- id: EntityId;
951
- data: TEntity;
952
- };
953
- detailFetch?: (id: EntityId) => Promise<TEntity>;
954
- onCreate?: (data: Partial<TEntity>) => Promise<TEntity>;
955
- onUpdate?: (id: EntityId, patch: Partial<TEntity>) => Promise<TEntity>;
956
- onDelete?: (id: EntityId) => Promise<void>;
957
- createDefaults?: Partial<TEntity>;
958
- initialView?: ViewDescriptor;
959
- onCreateSuccess?: (entity: TEntity) => void;
960
- onUpdateSuccess?: (entity: TEntity) => void;
961
- onDeleteSuccess?: (id: EntityId) => void;
962
- onError?: (op: "create" | "update" | "delete", error: Error) => void;
963
- selectAfterCreate?: boolean;
964
- clearSelectionAfterDelete?: boolean;
965
- }
966
- /** Tracks which fields diverge from loaded detail — edit buffer stays in React state so other views keep showing canonical graph data until save. */
967
- interface DirtyFields<TEntity> {
968
- changed: Set<keyof TEntity>;
969
- isDirty: boolean;
970
- }
971
- /**
972
- * Everything a CRUD screen needs: composed `list` view, selection, detail subscription, relation joins, edit/create buffers, and mutating actions.
973
- * `applyOptimistic` is the escape hatch to mirror the buffer into `patches` for instant sliders/toggles without committing `save` yet.
974
- */
975
- interface CRUDState<TEntity extends Record<string, unknown>> {
976
- mode: CRUDMode;
977
- setMode: (mode: CRUDMode) => void;
978
- list: UseEntityViewResult<TEntity>;
979
- selectedId: EntityId | null;
980
- select: (id: EntityId | null) => void;
981
- openDetail: (id: EntityId) => void;
982
- detail: TEntity | null;
983
- detailIsLoading: boolean;
984
- detailError: string | null;
985
- relations: Record<string, unknown>;
986
- editBuffer: Partial<TEntity>;
987
- setField: <K extends keyof TEntity>(field: K, value: TEntity[K]) => void;
988
- setFields: (fields: Partial<TEntity>) => void;
989
- resetBuffer: () => void;
990
- dirty: DirtyFields<TEntity>;
991
- startEdit: (id?: EntityId) => void;
992
- cancelEdit: () => void;
993
- save: () => Promise<TEntity | null>;
994
- isSaving: boolean;
995
- saveError: string | null;
996
- applyOptimistic: () => void;
997
- createBuffer: Partial<TEntity>;
998
- setCreateField: <K extends keyof TEntity>(field: K, value: TEntity[K]) => void;
999
- setCreateFields: (fields: Partial<TEntity>) => void;
1000
- resetCreateBuffer: () => void;
1001
- startCreate: () => void;
1002
- cancelCreate: () => void;
1003
- create: () => Promise<TEntity | null>;
1004
- isCreating: boolean;
1005
- createError: string | null;
1006
- deleteEntity: (id?: EntityId) => Promise<void>;
1007
- isDeleting: boolean;
1008
- deleteError: string | null;
1009
- isEditing: boolean;
1010
- }
1011
- /**
1012
- * Batteries-included CRUD orchestration over the entity graph: list filtering/sorting, detail fetch, isolated edit buffer, optimistic create row, and transactional save/delete with rollback.
1013
- * Prefer this over ad-hoc `useEntity` wiring when building admin-style tables + side panels + forms for one resource.
1014
- *
1015
- * @param opts - `CRUDOptions` for type, list key/fetch, normalization, lifecycle callbacks
1016
- * @returns `CRUDState` with list/detail/edit/create controls
1017
- */
1018
- declare function useEntityCRUD<TEntity extends Record<string, unknown>>(opts: CRUDOptions<TEntity>): CRUDState<TEntity>;
1019
-
1020
1233
  /**
1021
1234
  * FK edge: this entity points at one parent row. Used to invalidate parent aggregates and optional list keys when the FK changes.
1022
1235
  */
@@ -1528,50 +1741,6 @@ interface EntityTableProps<T extends Record<string, unknown>> {
1528
1741
  }
1529
1742
  declare function EntityTable<T extends Record<string, unknown>>({ viewResult, columns, getRowId, selectedId, onRowClick, onCellEdit, onBulkAction, paginationMode, pageSize, searchPlaceholder, searchFields, toolbarChildren, showToolbar, emptyState, className }: EntityTableProps<T>): react_jsx_runtime.JSX.Element;
1530
1743
 
1531
- type FieldType = "text" | "textarea" | "number" | "email" | "url" | "date" | "boolean" | "enum" | "custom";
1532
- interface FieldDescriptor<TEntity> {
1533
- field: keyof TEntity & string;
1534
- label: string;
1535
- type: FieldType;
1536
- required?: boolean;
1537
- placeholder?: string;
1538
- options?: Array<{
1539
- value: string;
1540
- label: string;
1541
- }>;
1542
- hint?: string;
1543
- render?: (value: unknown, entity: TEntity) => React$1.ReactNode;
1544
- editControl?: (value: unknown, onChange: (v: unknown) => void, entity: Partial<TEntity>) => React$1.ReactNode;
1545
- hideOnCreate?: boolean;
1546
- hideOnEdit?: boolean;
1547
- readonlyOnEdit?: boolean;
1548
- }
1549
- declare function Sheet({ open, onClose, title, subtitle, children, footer, width }: {
1550
- open: boolean;
1551
- onClose: () => void;
1552
- title: string;
1553
- subtitle?: string;
1554
- children: React$1.ReactNode;
1555
- footer?: React$1.ReactNode;
1556
- width?: string;
1557
- }): react_jsx_runtime.JSX.Element;
1558
- declare function EntityDetailSheet<TEntity extends Record<string, unknown>>({ crud, fields, title, description, children, showEditButton, showDeleteButton, deleteConfirmMessage }: {
1559
- crud: CRUDState<TEntity>;
1560
- fields: FieldDescriptor<TEntity>[];
1561
- title?: string | ((e: TEntity) => string);
1562
- description?: string | ((e: TEntity) => string);
1563
- children?: (entity: TEntity, crud: CRUDState<TEntity>) => React$1.ReactNode;
1564
- showEditButton?: boolean;
1565
- showDeleteButton?: boolean;
1566
- deleteConfirmMessage?: string;
1567
- }): react_jsx_runtime.JSX.Element;
1568
- declare function EntityFormSheet<TEntity extends Record<string, unknown>>({ crud, fields, createTitle, editTitle }: {
1569
- crud: CRUDState<TEntity>;
1570
- fields: FieldDescriptor<TEntity>[];
1571
- createTitle?: string;
1572
- editTitle?: string | ((e: TEntity) => string);
1573
- }): react_jsx_runtime.JSX.Element;
1574
-
1575
1744
  type ColumnFilterType = "text" | "number" | "date" | "dateRange" | "boolean" | "enum" | "relation" | "none";
1576
1745
  interface EntityColumnMeta<TEntity> {
1577
1746
  field: keyof TEntity;
@@ -2766,4 +2935,4 @@ declare const TableHead: React$1.ForwardRefExoticComponent<React$1.ThHTMLAttribu
2766
2935
  declare const TableCell: React$1.ForwardRefExoticComponent<React$1.TdHTMLAttributes<HTMLTableCellElement> & React$1.RefAttributes<HTMLTableCellElement>>;
2767
2936
  declare const TableCaption: React$1.ForwardRefExoticComponent<React$1.HTMLAttributes<HTMLTableCaptionElement> & React$1.RefAttributes<HTMLTableCaptionElement>>;
2768
2937
 
2769
- export { type AccessorFn, ActionButtonRow, type ActionDef, ActionDropdown, type ActionItem, type ActivePresets, type AdapterStatus, type AggregationFn, type BatchActionDef, type BelongsToRelation, type CRUDMode, type CRUDOptions, type CRUDState, type CascadeContext, type Cell, type CellContext, type CellRenderer, type ChangeOperation, type ChangeSet, type ChannelConfig, type Column, type ColumnFilterType, type ColumnFiltersState, type ColumnOrderState, type ColumnPinningState, type ColumnPreset, ColumnPresetDialog, type ColumnPresetEntry, type ColumnSizingState, type ColumnVisibilityState, type CompletenessMode, DataTable, DataTableColumnHeader, DataTableFilter, DataTablePagination, DataTableToolbar, type DirtyFields, type ElectricAdapterOptions, ElectricSQLAdapter as ElectricSQLPresetAdapter, type ElectricSQLAdapterOptions as ElectricSQLPresetAdapterOptions, type ElectricTableConfig, EmptyState, type EmptyStateConfig, type EngineOptions, type EntityChange, type EntityColumnMeta, type EntityDescriptor, EntityDetailSheet, EntityFormSheet, type EntityId, EntityListView, type EntityListViewProps, type EntityQueryOptions, type EntitySchema, type EntitySnapshot, type EntityState, type EntitySyncMetadata, EntityTable, type EntityType, type ExpandedState, type FieldDescriptor, type FieldType, type FilterClause, type FilterFn, type FilterGroup, type FilterOperator, type FilterPreset, FilterPresetDialog, type FilterSpec, GQLClient, type GQLClientConfig, type GQLEntityOptions, type GQLError, type GQLListOptions, type GQLResponse, type GalleryColumns, GalleryView, type GraphActionOptions, type GraphEffectEvent, type GraphEffectHandle, type GraphEffectOptions, type GraphIncludeMap, type GraphIncludeRelation, type GraphQueryOptions, type GraphSnapshotExportOptions, type GraphState, type GraphToolContext, type GraphTransaction, type GroupingState, type HasManyRelation, type Header, type HeaderContext, type HeaderGroup, type HeaderRenderer, InlineCellEditor$1 as InlineCellEditor, InlineItemEditor, type ItemDescriptor, type ItemDescriptorBadge, type ItemDescriptorMeta, type ItemRenderContext, type ListFetchParams, type ListQueryOptions, type ListResponse, type ListState, ListView, type ManagerOptions, type ManyToManyRelation, MemoryAdapter, MultiSelectBar, type PaginationState, type PresetChangeEvent, type PresetChangeOperation, PresetPicker, type PresetStoreState, type UnsubscribeFn as PresetUnsubscribeFn, type PrismaEntityConfigOptions, type ColumnDef as PureColumnDef, type ColumnMeta as PureColumnMeta, InlineCellEditor as PureInlineCellEditor, type QueryKey, type RealtimeAdapter, RealtimeManager, type RelationDescriptor, type RestAdapterOptions, RestApiAdapter, type Row, type RowModel, type RowSelectionState, SelectionContext, type SelectionStoreState, Sheet, type SortClause, type SortDirection, SortHeader, type SortSpec, type SortingFn, type SortingState, type SubscriptionConfig, type SupabaseAdapterOptions$1 as SupabaseAdapterOptions, SupabaseRealtimeAdapter as SupabasePresetAdapter, type SupabaseAdapterOptions as SupabasePresetAdapterOptions, type SyncAdapter, type SyncOrigin, Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, type TableInstance, type TableOptions, type TablePresetSlice, TableRow, type TableState, type TableStorageAdapter, TableStorageProvider, type TableStorageProviderProps, type UnsubscribeFn$1 as UnsubscribeFn, type Updater, type UseEntityViewOptions, type UseEntityViewResult, type UseLocalFirstResult, type UseTablePresetsOptions, type UseTablePresetsResult, type ViewDescriptor, type ViewFetchParams, type ViewMode, ViewModeSwitcher, type WebSocketAdapterOptions, type ZustandAdapterOptions, ZustandPersistAdapter, actionsColumn$1 as actionsColumn, applyView, booleanColumn$1 as booleanColumn, cascadeInvalidation, checkCompleteness, compareEntities, configureEngine, createConvexAdapter, createElectricAdapter, createGQLClient, createGraphAction, createGraphEffect, createGraphQLSubscriptionAdapter, createGraphTool, createGraphTransaction, createPresetStore, createPrismaEntityConfig, createRow, createSelectionStore, createSupabaseRealtimeAdapter, createWebSocketAdapter, dateColumn$1 as dateColumn, dedupe, deleteAction, editAction, enumColumn$1 as enumColumn, executeGQL, exportGraphSnapshot, fetchEntity, fetchList, flattenClauses, getCoreRowModel, getExpandedRowModel, getFacetedMinMaxValues, getFacetedRowModel, getFacetedUniqueValues, getFilteredRowModel, getGroupedRowModel, getPaginatedRowModel, getRealtimeManager, getSchema, getSelectedRowModel, getSortedRowModel, hasCustomPredicates, matchesFilter, matchesSearch, normalizeGQLResponse, numberColumn$1 as numberColumn, prismaRelationsToSchema, actionsColumn as pureActionsColumn, booleanColumn as pureBooleanColumn, dateColumn as pureDateColumn, enumColumn as pureEnumColumn, numberColumn as pureNumberColumn, selectionColumn as pureSelectionColumn, textColumn as pureTextColumn, queryOnce, readRelations, registerSchema, resetRealtimeManager, selectGraph, selectionColumn$1 as selectionColumn, serializeKey, startGarbageCollector, stopGarbageCollector, textColumn$1 as textColumn, toGraphQLVariables, toPrismaInclude, toPrismaOrderBy, toPrismaWhere, toRestParams, toSQLClauses, useEntity, useEntityAugment, useEntityCRUD, useEntityList, useEntityMutation, useEntityView, useGQLEntity, useGQLList, useGQLMutation, useGQLSubscription, useGraphDevTools, useGraphStore, useLocalFirst, usePGliteQuery, useSelectionContext, useSelectionStore, useSuspenseEntity, useSuspenseEntityList, useTable, useTablePresets, useTableRealtimeMode, useTableStorageAdapter, viewAction };
2938
+ export { type AccessorFn, ActionButtonRow, type ActionDef, ActionDropdown, type ActionItem, type ActivePresets, type AdapterStatus, type AggregationFn, type BatchActionDef, type BelongsToRelation, type BuildEntityFieldsFromSchemaOptions, type CRUDMode, type CRUDOptions, type CRUDState, type CascadeContext, type Cell, type CellContext, type CellRenderer, type ChangeOperation, type ChangeSet, type ChannelConfig, type Column, type ColumnFilterType, type ColumnFiltersState, type ColumnOrderState, type ColumnPinningState, type ColumnPreset, ColumnPresetDialog, type ColumnPresetEntry, type ColumnSizingState, type ColumnVisibilityState, type CompletenessMode, DataTable, DataTableColumnHeader, DataTableFilter, DataTablePagination, DataTableToolbar, type DirtyFields, type ElectricAdapterOptions, ElectricSQLAdapter as ElectricSQLPresetAdapter, type ElectricSQLAdapterOptions as ElectricSQLPresetAdapterOptions, type ElectricTableConfig, EmptyState, type EmptyStateConfig, type EngineOptions, type EntityChange, type EntityColumnMeta, type EntityDescriptor, EntityDetailSheet, EntityFormSheet, type EntityId, type EntityJsonSchemaConfig, EntityListView, type EntityListViewProps, type EntityQueryOptions, type EntitySchema, type EntitySnapshot, type EntityState, type EntitySyncMetadata, EntityTable, type EntityType, type ExpandedState, type FieldDescriptor, type FieldType, type FilterClause, type FilterFn, type FilterGroup, type FilterOperator, type FilterPreset, FilterPresetDialog, type FilterSpec, GQLClient, type GQLClientConfig, type GQLEntityOptions, type GQLError, type GQLListOptions, type GQLResponse, type GalleryColumns, GalleryView, type GraphActionEvent, type GraphActionOptions, type GraphActionRecord$1 as GraphActionRecord, type GraphEffectEvent, type GraphEffectHandle, type GraphEffectOptions, type GraphIncludeMap, type GraphIncludeRelation, type GraphPersistenceAdapter, type GraphQueryOptions, type GraphSnapshotExportOptions, type GraphSnapshotPayload, type GraphSnapshotWithSchemasOptions, type GraphState, type GraphSyncStatus, type GraphToolContext, type GraphTransaction, type GroupingState, type HasManyRelation, type Header, type HeaderContext, type HeaderGroup, type HeaderRenderer, InlineCellEditor$1 as InlineCellEditor, InlineItemEditor, type ItemDescriptor, type ItemDescriptorBadge, type ItemDescriptorMeta, type ItemRenderContext, type JsonSchemaObject, type ListFetchParams, type ListQueryOptions, type ListResponse, type ListState, ListView, type LocalFirstGraphRuntime, type ManagerOptions, type ManyToManyRelation, MarkdownFieldEditor, MarkdownFieldRenderer, MemoryAdapter, MultiSelectBar, type PaginationState, type GraphActionRecord as PersistedGraphActionRecord, type PresetChangeEvent, type PresetChangeOperation, PresetPicker, type PresetStoreState, type UnsubscribeFn as PresetUnsubscribeFn, type PrismaEntityConfigOptions, type ColumnDef as PureColumnDef, type ColumnMeta as PureColumnMeta, InlineCellEditor as PureInlineCellEditor, type QueryKey, type RealtimeAdapter, RealtimeManager, type RelationDescriptor, type RestAdapterOptions, RestApiAdapter, type Row, type RowModel, type RowSelectionState, type SchemaFieldDescriptor, type SchemaGraphToolContext, SelectionContext, type SelectionStoreState, Sheet, type SortClause, type SortDirection, SortHeader, type SortSpec, type SortingFn, type SortingState, type StartLocalFirstGraphOptions, type SubscriptionConfig, type SupabaseAdapterOptions$1 as SupabaseAdapterOptions, SupabaseRealtimeAdapter as SupabasePresetAdapter, type SupabaseAdapterOptions as SupabasePresetAdapterOptions, type SyncAdapter, type SyncOrigin, Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, type TableInstance, type TableOptions, type TablePresetSlice, TableRow, type TableState, type TableStorageAdapter, TableStorageProvider, type TableStorageProviderProps, type UnsubscribeFn$1 as UnsubscribeFn, type Updater, type UseEntityViewOptions, type UseEntityViewResult, type UseLocalFirstResult, type UseTablePresetsOptions, type UseTablePresetsResult, type ViewDescriptor, type ViewFetchParams, type ViewMode, ViewModeSwitcher, type WebSocketAdapterOptions, type ZustandAdapterOptions, ZustandPersistAdapter, actionsColumn$1 as actionsColumn, applyView, booleanColumn$1 as booleanColumn, buildEntityFieldsFromSchema, cascadeInvalidation, checkCompleteness, compareEntities, configureEngine, createConvexAdapter, createElectricAdapter, createGQLClient, createGraphAction, createGraphEffect, createGraphQLSubscriptionAdapter, createGraphTool, createGraphTransaction, createPresetStore, createPrismaEntityConfig, createRow, createSchemaGraphTool, createSelectionStore, createSupabaseRealtimeAdapter, createWebSocketAdapter, dateColumn$1 as dateColumn, dedupe, deleteAction, editAction, enumColumn$1 as enumColumn, executeGQL, exportGraphSnapshot, exportGraphSnapshotWithSchemas, fetchEntity, fetchList, flattenClauses, getCoreRowModel, getEntityJsonSchema, getExpandedRowModel, getFacetedMinMaxValues, getFacetedRowModel, getFacetedUniqueValues, getFilteredRowModel, getGroupedRowModel, getPaginatedRowModel, getRealtimeManager, getSchema, getSelectedRowModel, getSortedRowModel, hasCustomPredicates, hydrateGraphFromStorage, matchesFilter, matchesSearch, normalizeGQLResponse, numberColumn$1 as numberColumn, persistGraphToStorage, prismaRelationsToSchema, actionsColumn as pureActionsColumn, booleanColumn as pureBooleanColumn, dateColumn as pureDateColumn, enumColumn as pureEnumColumn, numberColumn as pureNumberColumn, selectionColumn as pureSelectionColumn, textColumn as pureTextColumn, queryOnce, readRelations, registerEntityJsonSchema, registerRuntimeSchema, registerSchema, renderMarkdownToHtml, resetRealtimeManager, selectGraph, selectionColumn$1 as selectionColumn, serializeKey, startGarbageCollector, startLocalFirstGraph, stopGarbageCollector, textColumn$1 as textColumn, toGraphQLVariables, toPrismaInclude, toPrismaOrderBy, toPrismaWhere, toRestParams, toSQLClauses, useEntity, useEntityAugment, useEntityCRUD, useEntityList, useEntityMutation, useEntityView, useGQLEntity, useGQLList, useGQLMutation, useGQLSubscription, useGraphDevTools, useGraphStore, useGraphSyncStatus, useLocalFirst, usePGliteQuery, useSchemaEntityFields, useSelectionContext, useSelectionStore, useSuspenseEntity, useSuspenseEntityList, useTable, useTablePresets, useTableRealtimeMode, useTableStorageAdapter, viewAction };