@almadar/ui 3.9.1 → 4.0.1

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.
@@ -2,50 +2,88 @@
2
2
  * SlotsContext - React state-based UI slot management
3
3
  *
4
4
  * Replaces the UIEnvironment observable store with plain React state.
5
- * No stacking logic, no priority system, no source tracking for arbitration.
5
+ * No stacking logic, no priority system.
6
6
  *
7
- * A transition's effects produce the COMPLETE content for each slot.
8
- * The runtime collects all render-ui effects from a transition, groups by slot,
9
- * and sets each slot's patterns array in one atomic operation.
7
+ * A transition's effects produce the COMPLETE content for each slot
8
+ * from the POV of the trait that fired. The runtime collects all render-ui
9
+ * effects from a transition, groups by slot, and calls
10
+ * `setSlotPatterns(slot, patterns, source)` once per slot.
11
+ *
12
+ * Multi-trait parity with the compiled path (2026-04-24):
13
+ * State is keyed by (slot, sourceTraitName). When ProbeCreate renders to
14
+ * "main" and ProbePersistor also renders to "main", both entries coexist —
15
+ * each source owns its own sub-entry. Consumers see them as a list in
16
+ * insertion order. Prior model was single-entry per slot (last-writer-wins),
17
+ * which diverged from the compiled path's VStack-of-trait-views layout.
10
18
  *
11
19
  * @packageDocumentation
12
20
  */
13
21
  import React from 'react';
14
- import type { PatternConfig } from '@almadar/core';
15
- import type { ResolvedTrait } from '@almadar/core';
22
+ import type { PatternConfig, EventSource, ResolvedTrait } from '@almadar/core';
16
23
  /** A single pattern entry in a slot */
17
24
  export interface SlotPatternEntry {
18
25
  pattern: PatternConfig;
19
26
  props: Record<string, unknown>;
20
27
  }
21
28
  /**
22
- * Source metadata for a rendered slot.
23
- * Used by the Slot Inspector to show debug info.
29
+ * Source metadata for a rendered slot. Extends `@almadar/core`'s
30
+ * `EventSource` (which owns the `{ trait, transition?, tick? }` shape
31
+ * shared with the event bus) with UI-only debug fields used by the Slot
32
+ * Inspector. Keeping the core subset aligned means the slot machinery
33
+ * can round-trip with event bus metadata without re-coercion.
24
34
  */
25
- export interface SlotSource {
26
- trait: string;
35
+ export interface SlotSource extends EventSource {
36
+ /** Source trait's current state at the moment of render. */
27
37
  state: string;
28
- transition: string;
38
+ /** Raw effects block that produced this render, for inspector display. */
29
39
  effects?: unknown[];
30
40
  /** Full trait definition for inspector */
31
41
  traitDefinition?: ResolvedTrait;
32
42
  }
33
- /** Full state of a single slot */
34
- export interface SlotState {
43
+ /**
44
+ * One source's contribution to a slot. The same slot can receive
45
+ * contributions from multiple source traits (e.g. a page-level layout
46
+ * where ProbeCreate and ProbePersistor both render to "main").
47
+ */
48
+ export interface SlotEntry {
35
49
  patterns: SlotPatternEntry[];
36
50
  source?: SlotSource;
37
51
  }
52
+ /**
53
+ * All source contributions to a single slot, keyed by the source trait's
54
+ * name. The sentinel `'__default__'` key is used when `setSlotPatterns`
55
+ * is called without a source (legacy / unscoped callers).
56
+ *
57
+ * Iteration order reflects insertion order — the first trait to write to
58
+ * the slot renders first, which matches the compiled-path VStack order
59
+ * set by the .lolo's `page ... -> Trait1, Trait2, ...` declaration.
60
+ */
61
+ export type SlotState = Record<string, SlotEntry>;
38
62
  /** All slots state */
39
63
  export type SlotsState = Record<string, SlotState>;
40
64
  /** Mutation functions for slots (stable references, won't trigger re-renders) */
41
65
  export interface SlotsActions {
42
- /** Set all patterns for a slot atomically (replaces previous content) */
66
+ /**
67
+ * Set this source's contribution to a slot atomically. Replaces any
68
+ * prior content FROM THE SAME SOURCE only; other sources' entries in
69
+ * the slot are untouched.
70
+ */
43
71
  setSlotPatterns: (slot: string, patterns: SlotPatternEntry[], source?: SlotSource) => void;
44
- /** Clear a single slot */
72
+ /**
73
+ * Clear a slot entirely. Wipes all source entries; consumers see an
74
+ * empty slot (no patterns from any source).
75
+ */
45
76
  clearSlot: (slot: string) => void;
77
+ /** Remove a single source's contribution from a slot, keeping others. */
78
+ clearSlotForSource: (slot: string, sourceTrait: string) => void;
46
79
  /** Clear all slots */
47
80
  clearAllSlots: () => void;
48
81
  }
82
+ /** Entries in render order for a slot (insertion order, empty-filtered). */
83
+ export declare function slotEntriesInOrder(slot: SlotState | null | undefined): Array<{
84
+ sourceKey: string;
85
+ entry: SlotEntry;
86
+ }>;
49
87
  export interface SlotsProviderProps {
50
88
  children: React.ReactNode;
51
89
  }
@@ -53,7 +91,7 @@ export interface SlotsProviderProps {
53
91
  * SlotsProvider - Manages UI slot state via React useState.
54
92
  *
55
93
  * Replaces UIEnvironmentProvider. No observable store, no stacking logic.
56
- * Slots are set atomically per transition — React diffs and re-renders.
94
+ * Slots are set atomically per (transition, source) — React diffs and re-renders.
57
95
  */
58
96
  export declare function SlotsProvider({ children }: SlotsProviderProps): React.ReactElement;
59
97
  /**
@@ -62,7 +100,9 @@ export declare function SlotsProvider({ children }: SlotsProviderProps): React.R
62
100
  */
63
101
  export declare function useSlots(): SlotsState;
64
102
  /**
65
- * Get content for a specific slot. Returns null if slot is empty.
103
+ * Get the per-source state map for a specific slot. Returns null if
104
+ * nothing has written to this slot yet. To iterate sources in render
105
+ * order, use `slotEntriesInOrder(useSlotContent(name))`.
66
106
  */
67
107
  export declare function useSlotContent(slotName: string): SlotState | null;
68
108
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@almadar/ui",
3
- "version": "3.9.1",
3
+ "version": "4.0.1",
4
4
  "description": "React UI components, hooks, and providers for Almadar",
5
5
  "type": "module",
6
6
  "main": "./dist/components/index.js",
@@ -1,155 +0,0 @@
1
- import type { ReactNode } from "react";
2
- import React from "react";
3
- export interface EntityDataAdapter {
4
- /** Get all records for an entity */
5
- getData: (entity: string) => Record<string, unknown>[];
6
- /** Get a single record by entity name and ID */
7
- getById: (entity: string, id: string) => Record<string, unknown> | undefined;
8
- /** Whether data is currently loading */
9
- isLoading: boolean;
10
- /** Current error */
11
- error: string | null;
12
- }
13
- /**
14
- * Provider that bridges a host app's data source to useEntityList/useEntity hooks.
15
- *
16
- * @example
17
- * ```tsx
18
- * // In builder runtime
19
- * const fetchedData = useFetchedData();
20
- * const adapter = {
21
- * getData: fetchedData.getData,
22
- * getById: fetchedData.getById,
23
- * isLoading: fetchedData.loading,
24
- * error: fetchedData.error,
25
- * };
26
- * <EntityDataProvider adapter={adapter}>
27
- * {children}
28
- * </EntityDataProvider>
29
- * ```
30
- */
31
- export declare function EntityDataProvider({ adapter, children, }: {
32
- adapter: EntityDataAdapter;
33
- children: ReactNode;
34
- }): React.FunctionComponentElement<React.ProviderProps<EntityDataAdapter | null>>;
35
- /**
36
- * Access the entity data adapter (null if no provider).
37
- */
38
- export declare function useEntityDataAdapter(): EntityDataAdapter | null;
39
- export declare const entityDataKeys: {
40
- all: readonly ["entities"];
41
- lists: () => readonly ["entities", "list"];
42
- list: (entity: string, filters?: Record<string, unknown>) => readonly ["entities", "list", string, Record<string, unknown> | undefined];
43
- details: () => readonly ["entities", "detail"];
44
- detail: (entity: string, id: string) => readonly ["entities", "detail", string, string];
45
- };
46
- export type EntityDataRecord = Record<string, unknown>;
47
- export interface UseEntityListOptions {
48
- /** Skip fetching */
49
- skip?: boolean;
50
- }
51
- export interface UseEntityListResult<T = Record<string, unknown>> {
52
- data: T[];
53
- isLoading: boolean;
54
- error: Error | null;
55
- refetch: () => void;
56
- }
57
- export interface UseEntityDetailResult<T = Record<string, unknown>> {
58
- data: T | null;
59
- isLoading: boolean;
60
- error: Error | null;
61
- refetch: () => void;
62
- }
63
- export interface PaginationParams {
64
- page: number;
65
- pageSize: number;
66
- search?: string;
67
- sortBy?: string;
68
- sortDirection?: "asc" | "desc";
69
- filters?: Record<string, unknown>;
70
- }
71
- export interface UsePaginatedEntityListResult<T = Record<string, unknown>> {
72
- data: T[];
73
- isLoading: boolean;
74
- error: Error | null;
75
- totalCount: number;
76
- totalPages: number;
77
- hasNextPage: boolean;
78
- hasPreviousPage: boolean;
79
- refetch: () => void;
80
- }
81
- /**
82
- * Hook for fetching entity list data.
83
- * Uses EntityDataContext if available, otherwise falls back to stub.
84
- */
85
- export declare function useEntityList<T = Record<string, unknown>>(entity: string | undefined, options?: UseEntityListOptions): UseEntityListResult<T>;
86
- /**
87
- * Hook for fetching a single entity by ID.
88
- * Uses EntityDataContext if available, otherwise falls back to stub.
89
- */
90
- export declare function useEntity<T = Record<string, unknown>>(entity: string | undefined, id: string | undefined): {
91
- data: T | null;
92
- isLoading: boolean;
93
- error: Error | null;
94
- };
95
- /**
96
- * Hook for fetching entity detail by ID (alias for useEntity with refetch).
97
- * Uses EntityDataContext if available, otherwise falls back to stub.
98
- */
99
- export declare function useEntityDetail<T = Record<string, unknown>>(entity: string | undefined, id: string | undefined): UseEntityDetailResult<T>;
100
- /**
101
- * Hook for fetching paginated entity list data.
102
- * Uses EntityDataContext if available (client-side pagination), otherwise falls back to stub.
103
- */
104
- export declare function usePaginatedEntityList<T = Record<string, unknown>>(entity: string | undefined, params: PaginationParams, options?: UseEntityListOptions): UsePaginatedEntityListResult<T>;
105
- /**
106
- * Suspense-compatible hook for fetching entity list data.
107
- *
108
- * Instead of returning `isLoading`/`error`, this hook **suspends** (throws a Promise)
109
- * when data is not ready. Use inside a `<Suspense>` boundary.
110
- *
111
- * Falls back to the adapter when available; otherwise suspends briefly for stub data.
112
- *
113
- * @example
114
- * ```tsx
115
- * <Suspense fallback={<Skeleton variant="table" />}>
116
- * <ErrorBoundary>
117
- * <TaskList entity="Task" />
118
- * </ErrorBoundary>
119
- * </Suspense>
120
- *
121
- * function TaskList({ entity }: { entity: string }) {
122
- * const { data } = useEntityListSuspense<Task>(entity);
123
- * // No loading check needed — Suspense handles it
124
- * return <DataTable data={data} ... />;
125
- * }
126
- * ```
127
- */
128
- export declare function useEntityListSuspense<T = Record<string, unknown>>(entity: string): {
129
- data: T[];
130
- refetch: () => void;
131
- };
132
- /**
133
- * Suspense-compatible hook for fetching a single entity by ID.
134
- *
135
- * Suspends when data is not ready. Use inside a `<Suspense>` boundary.
136
- *
137
- * @example
138
- * ```tsx
139
- * <Suspense fallback={<Skeleton variant="form" />}>
140
- * <ErrorBoundary>
141
- * <TaskDetail entity="Task" id={taskId} />
142
- * </ErrorBoundary>
143
- * </Suspense>
144
- *
145
- * function TaskDetail({ entity, id }: { entity: string; id: string }) {
146
- * const { data } = useEntitySuspense<Task>(entity, id);
147
- * return <DetailPanel data={data} ... />;
148
- * }
149
- * ```
150
- */
151
- export declare function useEntitySuspense<T = Record<string, unknown>>(entity: string, id: string): {
152
- data: T | null;
153
- refetch: () => void;
154
- };
155
- export default useEntityList;
@@ -1,80 +0,0 @@
1
- /**
2
- * useEntityMutations - Entity CRUD mutation hooks
3
- *
4
- * Provides create, update, and delete mutations for entities.
5
- * Used by trait hooks for persist_data effects.
6
- *
7
- * @deprecated For new code, prefer useOrbitalMutations which sends mutations
8
- * through the orbital events route. This ensures all mutations go through
9
- * trait state machines and enforce guards/effects.
10
- *
11
- * Migration:
12
- * ```tsx
13
- * // Old (deprecated):
14
- * const { createEntity } = useEntityMutations('Task');
15
- *
16
- * // New (recommended):
17
- * const { createEntity } = useOrbitalMutations('Task', 'TaskManager');
18
- * ```
19
- *
20
- * @see useOrbitalMutations
21
- */
22
- import { type OrbitalEventResponse } from './useOrbitalMutations';
23
- export interface EntityMutationResult {
24
- id: string;
25
- [key: string]: unknown;
26
- }
27
- /**
28
- * Hook for creating entities
29
- */
30
- export declare function useCreateEntity(entityName: string): import("@tanstack/react-query").UseMutationResult<EntityMutationResult, Error, Record<string, unknown>, unknown>;
31
- /**
32
- * Hook for updating entities
33
- */
34
- export declare function useUpdateEntity(entityName: string): import("@tanstack/react-query").UseMutationResult<EntityMutationResult, Error, {
35
- id: string;
36
- data: Record<string, unknown>;
37
- }, unknown>;
38
- /**
39
- * Hook for deleting entities
40
- */
41
- export declare function useDeleteEntity(entityName: string): import("@tanstack/react-query").UseMutationResult<{
42
- id: string;
43
- }, Error, string, unknown>;
44
- export interface UseEntityMutationsOptions {
45
- /**
46
- * If provided, mutations go through orbital events route instead of direct CRUD.
47
- * This is the recommended approach for Phase 7+ compliance.
48
- */
49
- orbitalName?: string;
50
- /**
51
- * Custom event names when using orbital-based mutations
52
- */
53
- events?: {
54
- create?: string;
55
- update?: string;
56
- delete?: string;
57
- };
58
- }
59
- /**
60
- * Combined hook that provides all entity mutations
61
- * Used by trait hooks for persist_data effects
62
- *
63
- * @param entityName - The entity type name
64
- * @param options - Optional configuration including orbital-based mutations
65
- *
66
- * @deprecated For new code, prefer useOrbitalMutations directly
67
- */
68
- export declare function useEntityMutations(entityName: string, options?: UseEntityMutationsOptions): {
69
- createEntity: (entityOrData: string | Record<string, unknown>, data?: Record<string, unknown>) => Promise<OrbitalEventResponse | EntityMutationResult | undefined>;
70
- updateEntity: (id: string | undefined, data: Record<string, unknown>) => Promise<OrbitalEventResponse | EntityMutationResult | undefined>;
71
- deleteEntity: (id: string | undefined) => Promise<OrbitalEventResponse | {
72
- id: string;
73
- } | undefined>;
74
- isCreating: boolean;
75
- isUpdating: boolean;
76
- isDeleting: boolean;
77
- createError: Error | null;
78
- updateError: Error | null;
79
- deleteError: Error | null;
80
- };
@@ -1,95 +0,0 @@
1
- /**
2
- * useOrbitalMutations - Event-based entity mutations via orbital events route
3
- *
4
- * This hook provides entity mutations that go through the orbital events route
5
- * instead of direct CRUD API calls. This ensures all mutations:
6
- * 1. Go through trait state machines
7
- * 2. Enforce guards
8
- * 3. Execute all trait effects (including persist)
9
- *
10
- * This is the Phase 7 replacement for direct CRUD mutations.
11
- *
12
- * @example
13
- * ```tsx
14
- * const { createEntity, updateEntity, deleteEntity } = useOrbitalMutations('Task', 'TaskManager');
15
- *
16
- * // Create - sends ENTITY_CREATE event to orbital
17
- * await createEntity({ title: 'New Task', status: 'pending' });
18
- *
19
- * // Update - sends ENTITY_UPDATE event to orbital
20
- * await updateEntity(taskId, { status: 'completed' });
21
- *
22
- * // Delete - sends ENTITY_DELETE event to orbital
23
- * await deleteEntity(taskId);
24
- * ```
25
- *
26
- * @packageDocumentation
27
- */
28
- /**
29
- * Standard events for entity mutations
30
- * These are handled by orbitals with CRUD-capable traits
31
- */
32
- export declare const ENTITY_EVENTS: {
33
- readonly CREATE: "ENTITY_CREATE";
34
- readonly UPDATE: "ENTITY_UPDATE";
35
- readonly DELETE: "ENTITY_DELETE";
36
- };
37
- export interface OrbitalEventPayload {
38
- event: string;
39
- payload?: Record<string, unknown>;
40
- entityId?: string;
41
- }
42
- export interface OrbitalEventResponse {
43
- success: boolean;
44
- transitioned: boolean;
45
- states: Record<string, string>;
46
- emittedEvents: Array<{
47
- event: string;
48
- payload?: unknown;
49
- }>;
50
- error?: string;
51
- }
52
- /**
53
- * Hook for event-based entity mutations via orbital events route
54
- *
55
- * @param entityName - The entity type name (for cache invalidation)
56
- * @param orbitalName - The orbital to send events to
57
- * @param options - Optional configuration
58
- */
59
- export declare function useOrbitalMutations(entityName: string, orbitalName: string, options?: {
60
- /** Custom event names for create/update/delete */
61
- events?: {
62
- create?: string;
63
- update?: string;
64
- delete?: string;
65
- };
66
- /** Enable debug logging */
67
- debug?: boolean;
68
- }): {
69
- createEntity: (data: Record<string, unknown>) => Promise<OrbitalEventResponse>;
70
- updateEntity: (id: string | undefined, data: Record<string, unknown>) => Promise<OrbitalEventResponse | undefined>;
71
- deleteEntity: (id: string | undefined) => Promise<OrbitalEventResponse | undefined>;
72
- createMutation: import("@tanstack/react-query").UseMutationResult<OrbitalEventResponse, Error, Record<string, unknown>, unknown>;
73
- updateMutation: import("@tanstack/react-query").UseMutationResult<OrbitalEventResponse, Error, {
74
- id: string;
75
- data: Record<string, unknown>;
76
- }, unknown>;
77
- deleteMutation: import("@tanstack/react-query").UseMutationResult<OrbitalEventResponse, Error, string, unknown>;
78
- isCreating: boolean;
79
- isUpdating: boolean;
80
- isDeleting: boolean;
81
- isMutating: boolean;
82
- createError: Error | null;
83
- updateError: Error | null;
84
- deleteError: Error | null;
85
- };
86
- /**
87
- * Send a custom event to an orbital
88
- * For non-CRUD operations that go through trait state machines
89
- */
90
- export declare function useSendOrbitalEvent(orbitalName: string): {
91
- sendEvent: (event: string, payload?: Record<string, unknown>, entityId?: string) => Promise<OrbitalEventResponse>;
92
- isPending: boolean;
93
- error: Error | null;
94
- data: OrbitalEventResponse | undefined;
95
- };
@@ -1,32 +0,0 @@
1
- export interface ResolvedEntity<T> {
2
- /** Resolved data array */
3
- data: T[];
4
- /** True when data was provided directly via props (not fetched) */
5
- isLocal: boolean;
6
- /** Loading state — always false for local data */
7
- isLoading: boolean;
8
- /** Error state — always null for local data */
9
- error: Error | null;
10
- }
11
- /**
12
- * Resolves entity data from either a direct `data` prop or an `entity` string.
13
- *
14
- * When `data` is provided, it is used directly (isLocal: true).
15
- * When only `entity` (string) is provided, data is fetched via useEntityList.
16
- * Direct `data` always takes precedence over auto-fetch.
17
- *
18
- * @param entity - Entity name string for auto-fetch, or undefined
19
- * @param data - Direct data array from trait render-ui, or undefined
20
- * @returns Normalized { data, isLocal, isLoading, error }
21
- *
22
- * @example
23
- * ```tsx
24
- * function MyOrganism({ entity, data, isLoading, error }: MyProps) {
25
- * const resolved = useResolvedEntity<Item>(entity, data);
26
- * // resolved.data is always T[] regardless of source
27
- * // resolved.isLocal tells you if data came from props
28
- * }
29
- * ```
30
- */
31
- export declare function useResolvedEntity<T = Record<string, unknown>>(entity: string | undefined, data: readonly T[] | T[] | undefined): ResolvedEntity<T>;
32
- export default useResolvedEntity;
@@ -1,105 +0,0 @@
1
- /**
2
- * FetchedDataProvider
3
- *
4
- * Provides server-fetched entity data to the client runtime.
5
- * This context stores data returned from compiled event handlers
6
- * via the `data` field in EventResponse.
7
- *
8
- * Data Flow:
9
- * 1. Client sends event to server
10
- * 2. Server executes compiled handler with fetch effects
11
- * 3. Server returns { data: { EntityName: [...records] }, clientEffects: [...] }
12
- * 4. Provider stores data in this context
13
- * 5. Pattern components access data via useFetchedData hook
14
- *
15
- * Used by both Builder preview and compiled shell.
16
- *
17
- * @packageDocumentation
18
- */
19
- import React from 'react';
20
- export interface EntityRecord {
21
- id: string;
22
- [key: string]: unknown;
23
- }
24
- export interface FetchedDataState {
25
- /** Entity data by entity name (e.g., { Task: [...], User: [...] }) */
26
- data: Record<string, EntityRecord[]>;
27
- /** Timestamp of last fetch per entity */
28
- fetchedAt: Record<string, number>;
29
- /** Whether data is currently being fetched */
30
- loading: boolean;
31
- /** Last error message */
32
- error: string | null;
33
- }
34
- export interface FetchedDataContextValue {
35
- /** Get all records for an entity */
36
- getData: (entityName: string) => EntityRecord[];
37
- /** Get a single record by ID */
38
- getById: (entityName: string, id: string) => EntityRecord | undefined;
39
- /** Check if entity data exists */
40
- hasData: (entityName: string) => boolean;
41
- /** Get fetch timestamp for entity */
42
- getFetchedAt: (entityName: string) => number | undefined;
43
- /** Update data from server response */
44
- setData: (data: Record<string, unknown[]>) => void;
45
- /** Clear all fetched data */
46
- clearData: () => void;
47
- /** Clear data for specific entity */
48
- clearEntity: (entityName: string) => void;
49
- /** Current loading state */
50
- loading: boolean;
51
- /** Set loading state */
52
- setLoading: (loading: boolean) => void;
53
- /** Current error */
54
- error: string | null;
55
- /** Set error */
56
- setError: (error: string | null) => void;
57
- }
58
- export declare const FetchedDataContext: React.Context<FetchedDataContextValue | null>;
59
- export interface FetchedDataProviderProps {
60
- /** Initial data (optional) */
61
- initialData?: Record<string, unknown[]>;
62
- /** Children */
63
- children: React.ReactNode;
64
- }
65
- /**
66
- * FetchedDataProvider - Provides server-fetched entity data
67
- *
68
- * @example
69
- * ```tsx
70
- * <FetchedDataProvider>
71
- * <OrbitalProvider>
72
- * <App />
73
- * </OrbitalProvider>
74
- * </FetchedDataProvider>
75
- * ```
76
- */
77
- export declare function FetchedDataProvider({ initialData, children, }: FetchedDataProviderProps): React.ReactElement;
78
- /**
79
- * Access the fetched data context.
80
- * Returns null if not within a FetchedDataProvider.
81
- */
82
- export declare function useFetchedDataContext(): FetchedDataContextValue | null;
83
- /**
84
- * Access fetched data with fallback behavior.
85
- * If not in a provider, returns empty data.
86
- */
87
- export declare function useFetchedData(): FetchedDataContextValue;
88
- /**
89
- * Access fetched data for a specific entity.
90
- * Provides a convenient API for entity-specific operations.
91
- */
92
- export declare function useFetchedEntity(entityName: string): {
93
- /** All fetched records for this entity */
94
- records: EntityRecord[];
95
- /** Get a record by ID */
96
- getById: (id: string) => EntityRecord | undefined;
97
- /** Whether data has been fetched for this entity */
98
- hasData: boolean;
99
- /** When data was last fetched */
100
- fetchedAt: number | undefined;
101
- /** Whether data is loading */
102
- loading: boolean;
103
- /** Current error */
104
- error: string | null;
105
- };