@stigmer/react 3.11.1-dev.20260812192248 → 3.12.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.
- package/activity/useRecentActivity.d.ts +3 -2
- package/activity/useRecentActivity.d.ts.map +1 -1
- package/activity/useRecentActivity.js +3 -2
- package/activity/useRecentActivity.js.map +1 -1
- package/agent/AgentDetailView.d.ts +2 -12
- package/agent/AgentDetailView.d.ts.map +1 -1
- package/agent/AgentDetailView.js +10 -42
- package/agent/AgentDetailView.js.map +1 -1
- package/agent/internal/agentToInput.d.ts.map +1 -1
- package/agent/internal/agentToInput.js +0 -8
- package/agent/internal/agentToInput.js.map +1 -1
- package/dependency-graph/DependencyTreeNode.d.ts.map +1 -1
- package/dependency-graph/DependencyTreeNode.js +1 -7
- package/dependency-graph/DependencyTreeNode.js.map +1 -1
- package/dependency-graph/types.d.ts +8 -20
- package/dependency-graph/types.d.ts.map +1 -1
- package/dependency-graph/useDependencyGraph.d.ts.map +1 -1
- package/dependency-graph/useDependencyGraph.js +2 -20
- package/dependency-graph/useDependencyGraph.js.map +1 -1
- package/execution/useCreateAgentExecution.d.ts +3 -2
- package/execution/useCreateAgentExecution.d.ts.map +1 -1
- package/execution/useCreateAgentExecution.js.map +1 -1
- package/index.d.ts +0 -2
- package/index.d.ts.map +1 -1
- package/index.js +0 -6
- package/index.js.map +1 -1
- package/internal/isUnimplemented.d.ts +17 -0
- package/internal/isUnimplemented.d.ts.map +1 -0
- package/internal/isUnimplemented.js +26 -0
- package/internal/isUnimplemented.js.map +1 -0
- package/library/useExportResource.d.ts +3 -4
- package/library/useExportResource.d.ts.map +1 -1
- package/library/useExportResource.js +1 -1
- package/library/useExportResource.js.map +1 -1
- package/manifest/EditResourceYamlDialog.d.ts.map +1 -1
- package/manifest/EditResourceYamlDialog.js.map +1 -1
- package/mcp-server/McpServerConnectDialog.js +7 -3
- package/mcp-server/McpServerConnectDialog.js.map +1 -1
- package/mcp-server/useMcpServerCredentials.d.ts +38 -10
- package/mcp-server/useMcpServerCredentials.d.ts.map +1 -1
- package/mcp-server/useMcpServerCredentials.js +50 -7
- package/mcp-server/useMcpServerCredentials.js.map +1 -1
- package/mcp-server/useOrgOAuthApp.d.ts +22 -0
- package/mcp-server/useOrgOAuthApp.d.ts.map +1 -1
- package/mcp-server/useOrgOAuthApp.js +39 -7
- package/mcp-server/useOrgOAuthApp.js.map +1 -1
- package/package.json +4 -4
- package/session/useSessionConversation.d.ts +4 -2
- package/session/useSessionConversation.d.ts.map +1 -1
- package/session/useSessionConversation.js.map +1 -1
- package/skill/useSkillVersions.d.ts.map +1 -1
- package/skill/useSkillVersions.js +1 -9
- package/skill/useSkillVersions.js.map +1 -1
- package/src/activity/useRecentActivity.ts +3 -2
- package/src/agent/AgentDetailView.tsx +6 -130
- package/src/agent/internal/__tests__/agentToInput.test.ts +2 -11
- package/src/agent/internal/agentToInput.ts +0 -11
- package/src/dependency-graph/DependencyTreeNode.tsx +1 -7
- package/src/dependency-graph/__tests__/useDependencyGraph.test.ts +18 -34
- package/src/dependency-graph/types.ts +8 -20
- package/src/dependency-graph/useDependencyGraph.ts +2 -22
- package/src/execution/useCreateAgentExecution.ts +3 -2
- package/src/index.ts +0 -82
- package/src/internal/isUnimplemented.ts +26 -0
- package/src/library/useExportResource.ts +3 -4
- package/src/manifest/EditResourceYamlDialog.tsx +1 -2
- package/src/manifest/__tests__/ApplyManifestDialog.test.tsx +2 -2
- package/src/manifest/__tests__/EditResourceYamlDialog.test.tsx +9 -9
- package/src/mcp-server/McpServerConnectDialog.tsx +29 -11
- package/src/mcp-server/__tests__/VendorApprovalBlocked.surfaces.test.tsx +103 -5
- package/src/mcp-server/__tests__/byoaEditionDegradation.test.tsx +323 -0
- package/src/mcp-server/__tests__/useMcpServerCredentials.test.tsx +306 -0
- package/src/mcp-server/useMcpServerCredentials.ts +87 -19
- package/src/mcp-server/useOrgOAuthApp.ts +54 -8
- package/src/session/useSessionConversation.ts +4 -2
- package/src/skill/useSkillVersions.ts +1 -11
- package/src/workflow/WorkflowCanvasEditor.tsx +37 -7
- package/src/workflow/WorkflowExecutionGraph.tsx +26 -1
- package/src/workflow/WorkflowExecutionViewer.tsx +27 -13
- package/src/workflow/WorkflowNode.tsx +5 -2
- package/src/workflow/WorkflowRunDialog.tsx +28 -1
- package/src/workflow/__tests__/WorkflowExecutionViewer.reconciliation.test.tsx +48 -1
- package/src/workflow/__tests__/WorkflowNode.test.tsx +93 -0
- package/src/workflow/__tests__/WorkflowRunDialog.test.tsx +127 -0
- package/src/workflow/__tests__/graph-history.test.ts +103 -0
- package/src/workflow/__tests__/overview-graph.test.ts +17 -0
- package/src/workflow/__tests__/useCanvasKeyboardShortcuts.undo.test.ts +115 -0
- package/src/workflow/__tests__/useWorkflowCanvas.undo.test.tsx +133 -0
- package/src/workflow/instance/CreateWorkflowInstanceDialog.tsx +16 -7
- package/src/workflow/instance/useDeleteWorkflowInstance.ts +9 -4
- package/src/workflow/layout/registry-dimensions.ts +2 -5
- package/src/workflow/node-shell/NodeShell.tsx +4 -0
- package/src/workflow/task-type-visual-registry.ts +5 -3
- package/src/workflow/useCanvasKeyboardShortcuts.ts +33 -3
- package/src/workflow/useGraphHistory.ts +22 -41
- package/src/workflow/useWorkflowCanvas.ts +12 -10
- package/src/workflow/useWorkflowSave.ts +2 -15
- package/src/workflow/workflow-graph-conversions.ts +25 -3
- package/styles.css +1 -1
- package/workflow/CanvasTransitionEdge.d.ts +1 -1
- package/workflow/WorkflowCanvasEditor.d.ts.map +1 -1
- package/workflow/WorkflowCanvasEditor.js +31 -6
- package/workflow/WorkflowCanvasEditor.js.map +1 -1
- package/workflow/WorkflowExecutionGraph.d.ts.map +1 -1
- package/workflow/WorkflowExecutionGraph.js +27 -1
- package/workflow/WorkflowExecutionGraph.js.map +1 -1
- package/workflow/WorkflowExecutionViewer.d.ts.map +1 -1
- package/workflow/WorkflowExecutionViewer.js +26 -12
- package/workflow/WorkflowExecutionViewer.js.map +1 -1
- package/workflow/WorkflowNode.d.ts.map +1 -1
- package/workflow/WorkflowNode.js +2 -2
- package/workflow/WorkflowNode.js.map +1 -1
- package/workflow/WorkflowRunDialog.d.ts +11 -1
- package/workflow/WorkflowRunDialog.d.ts.map +1 -1
- package/workflow/WorkflowRunDialog.js +16 -2
- package/workflow/WorkflowRunDialog.js.map +1 -1
- package/workflow/instance/CreateWorkflowInstanceDialog.d.ts.map +1 -1
- package/workflow/instance/CreateWorkflowInstanceDialog.js +10 -2
- package/workflow/instance/CreateWorkflowInstanceDialog.js.map +1 -1
- package/workflow/instance/useDeleteWorkflowInstance.d.ts +9 -4
- package/workflow/instance/useDeleteWorkflowInstance.d.ts.map +1 -1
- package/workflow/instance/useDeleteWorkflowInstance.js +4 -2
- package/workflow/instance/useDeleteWorkflowInstance.js.map +1 -1
- package/workflow/layout/registry-dimensions.d.ts.map +1 -1
- package/workflow/layout/registry-dimensions.js +2 -5
- package/workflow/layout/registry-dimensions.js.map +1 -1
- package/workflow/node-shell/NodeShell.js +1 -1
- package/workflow/node-shell/NodeShell.js.map +1 -1
- package/workflow/task-type-visual-registry.d.ts.map +1 -1
- package/workflow/task-type-visual-registry.js +5 -3
- package/workflow/task-type-visual-registry.js.map +1 -1
- package/workflow/useCanvasKeyboardShortcuts.d.ts +15 -4
- package/workflow/useCanvasKeyboardShortcuts.d.ts.map +1 -1
- package/workflow/useCanvasKeyboardShortcuts.js +25 -4
- package/workflow/useCanvasKeyboardShortcuts.js.map +1 -1
- package/workflow/useGraphHistory.d.ts +16 -9
- package/workflow/useGraphHistory.d.ts.map +1 -1
- package/workflow/useGraphHistory.js +19 -33
- package/workflow/useGraphHistory.js.map +1 -1
- package/workflow/useWorkflowCanvas.d.ts +1 -3
- package/workflow/useWorkflowCanvas.d.ts.map +1 -1
- package/workflow/useWorkflowCanvas.js +15 -9
- package/workflow/useWorkflowCanvas.js.map +1 -1
- package/workflow/useWorkflowSave.d.ts.map +1 -1
- package/workflow/useWorkflowSave.js +2 -15
- package/workflow/useWorkflowSave.js.map +1 -1
- package/workflow/workflow-graph-conversions.d.ts +19 -1
- package/workflow/workflow-graph-conversions.d.ts.map +1 -1
- package/workflow/workflow-graph-conversions.js +17 -3
- package/workflow/workflow-graph-conversions.js.map +1 -1
- package/datastore/CollectionRecordsBrowser.d.ts +0 -34
- package/datastore/CollectionRecordsBrowser.d.ts.map +0 -1
- package/datastore/CollectionRecordsBrowser.js +0 -226
- package/datastore/CollectionRecordsBrowser.js.map +0 -1
- package/datastore/CollectionSchemaView.d.ts +0 -29
- package/datastore/CollectionSchemaView.d.ts.map +0 -1
- package/datastore/CollectionSchemaView.js +0 -93
- package/datastore/CollectionSchemaView.js.map +0 -1
- package/datastore/DatastoreDetailView.d.ts +0 -44
- package/datastore/DatastoreDetailView.d.ts.map +0 -1
- package/datastore/DatastoreDetailView.js +0 -76
- package/datastore/DatastoreDetailView.js.map +0 -1
- package/datastore/DatastoreSyncReport.d.ts +0 -19
- package/datastore/DatastoreSyncReport.d.ts.map +0 -1
- package/datastore/DatastoreSyncReport.js +0 -45
- package/datastore/DatastoreSyncReport.js.map +0 -1
- package/datastore/DeleteDatastoreDialog.d.ts +0 -35
- package/datastore/DeleteDatastoreDialog.d.ts.map +0 -1
- package/datastore/DeleteDatastoreDialog.js +0 -90
- package/datastore/DeleteDatastoreDialog.js.map +0 -1
- package/datastore/FieldValueControl.d.ts +0 -55
- package/datastore/FieldValueControl.d.ts.map +0 -1
- package/datastore/FieldValueControl.js +0 -138
- package/datastore/FieldValueControl.js.map +0 -1
- package/datastore/RecordFilterBuilder.d.ts +0 -37
- package/datastore/RecordFilterBuilder.d.ts.map +0 -1
- package/datastore/RecordFilterBuilder.js +0 -122
- package/datastore/RecordFilterBuilder.js.map +0 -1
- package/datastore/RecordFormPanel.d.ts +0 -45
- package/datastore/RecordFormPanel.d.ts.map +0 -1
- package/datastore/RecordFormPanel.js +0 -215
- package/datastore/RecordFormPanel.js.map +0 -1
- package/datastore/index.d.ts +0 -43
- package/datastore/index.d.ts.map +0 -1
- package/datastore/index.js +0 -22
- package/datastore/index.js.map +0 -1
- package/datastore/recordFilter.d.ts +0 -62
- package/datastore/recordFilter.d.ts.map +0 -1
- package/datastore/recordFilter.js +0 -104
- package/datastore/recordFilter.js.map +0 -1
- package/datastore/recordValues.d.ts +0 -85
- package/datastore/recordValues.d.ts.map +0 -1
- package/datastore/recordValues.js +0 -282
- package/datastore/recordValues.js.map +0 -1
- package/datastore/useDatastore.d.ts +0 -39
- package/datastore/useDatastore.d.ts.map +0 -1
- package/datastore/useDatastore.js +0 -44
- package/datastore/useDatastore.js.map +0 -1
- package/datastore/useDatastoreCount.d.ts +0 -50
- package/datastore/useDatastoreCount.d.ts.map +0 -1
- package/datastore/useDatastoreCount.js +0 -27
- package/datastore/useDatastoreCount.js.map +0 -1
- package/datastore/useDatastoreDescription.d.ts +0 -54
- package/datastore/useDatastoreDescription.d.ts.map +0 -1
- package/datastore/useDatastoreDescription.js +0 -50
- package/datastore/useDatastoreDescription.js.map +0 -1
- package/datastore/useDatastoreList.d.ts +0 -57
- package/datastore/useDatastoreList.d.ts.map +0 -1
- package/datastore/useDatastoreList.js +0 -29
- package/datastore/useDatastoreList.js.map +0 -1
- package/datastore/useDatastoreSearch.d.ts +0 -19
- package/datastore/useDatastoreSearch.d.ts.map +0 -1
- package/datastore/useDatastoreSearch.js +0 -22
- package/datastore/useDatastoreSearch.js.map +0 -1
- package/datastore/useDeleteDatastore.d.ts +0 -53
- package/datastore/useDeleteDatastore.d.ts.map +0 -1
- package/datastore/useDeleteDatastore.js +0 -40
- package/datastore/useDeleteDatastore.js.map +0 -1
- package/datastore/useDeleteRecord.d.ts +0 -34
- package/datastore/useDeleteRecord.d.ts.map +0 -1
- package/datastore/useDeleteRecord.js +0 -43
- package/datastore/useDeleteRecord.js.map +0 -1
- package/datastore/useInsertRecord.d.ts +0 -50
- package/datastore/useInsertRecord.d.ts.map +0 -1
- package/datastore/useInsertRecord.js +0 -55
- package/datastore/useInsertRecord.js.map +0 -1
- package/datastore/useRecordCollection.d.ts +0 -104
- package/datastore/useRecordCollection.d.ts.map +0 -1
- package/datastore/useRecordCollection.js +0 -136
- package/datastore/useRecordCollection.js.map +0 -1
- package/datastore/useRecordList.d.ts +0 -66
- package/datastore/useRecordList.d.ts.map +0 -1
- package/datastore/useRecordList.js +0 -58
- package/datastore/useRecordList.js.map +0 -1
- package/datastore/useUpdateRecord.d.ts +0 -52
- package/datastore/useUpdateRecord.d.ts.map +0 -1
- package/datastore/useUpdateRecord.js +0 -55
- package/datastore/useUpdateRecord.js.map +0 -1
- package/src/agent/__tests__/AgentDetailView.datastores.test.tsx +0 -166
- package/src/datastore/CollectionRecordsBrowser.tsx +0 -571
- package/src/datastore/CollectionSchemaView.tsx +0 -247
- package/src/datastore/DatastoreDetailView.tsx +0 -201
- package/src/datastore/DatastoreSyncReport.tsx +0 -105
- package/src/datastore/DeleteDatastoreDialog.tsx +0 -202
- package/src/datastore/FieldValueControl.tsx +0 -289
- package/src/datastore/RecordFilterBuilder.tsx +0 -422
- package/src/datastore/RecordFormPanel.tsx +0 -432
- package/src/datastore/__tests__/CollectionRecordsBrowser.test.tsx +0 -245
- package/src/datastore/__tests__/DeleteDatastoreDialog.test.tsx +0 -133
- package/src/datastore/__tests__/FieldValueControl.test.tsx +0 -160
- package/src/datastore/__tests__/RecordFilterBuilder.test.tsx +0 -155
- package/src/datastore/__tests__/RecordFormPanel.test.tsx +0 -227
- package/src/datastore/__tests__/recordFilter.test.ts +0 -169
- package/src/datastore/__tests__/recordMutations.test.tsx +0 -195
- package/src/datastore/__tests__/recordValues.test.ts +0 -359
- package/src/datastore/__tests__/schemaAndSyncViews.test.tsx +0 -142
- package/src/datastore/__tests__/useDatastoreDescription.test.tsx +0 -143
- package/src/datastore/__tests__/useRecordCollection.test.tsx +0 -250
- package/src/datastore/__tests__/useRecordList.test.tsx +0 -85
- package/src/datastore/index.ts +0 -111
- package/src/datastore/recordFilter.ts +0 -154
- package/src/datastore/recordValues.ts +0 -327
- package/src/datastore/useDatastore.ts +0 -67
- package/src/datastore/useDatastoreCount.ts +0 -69
- package/src/datastore/useDatastoreDescription.ts +0 -90
- package/src/datastore/useDatastoreList.ts +0 -79
- package/src/datastore/useDatastoreSearch.ts +0 -39
- package/src/datastore/useDeleteDatastore.ts +0 -87
- package/src/datastore/useDeleteRecord.ts +0 -77
- package/src/datastore/useInsertRecord.ts +0 -92
- package/src/datastore/useRecordCollection.ts +0 -256
- package/src/datastore/useRecordList.ts +0 -131
- package/src/datastore/useUpdateRecord.ts +0 -95
|
@@ -1,250 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
2
|
-
import { renderHook, waitFor, act } from "@testing-library/react";
|
|
3
|
-
import type { ReactNode } from "react";
|
|
4
|
-
import { create } from "@bufbuild/protobuf";
|
|
5
|
-
import {
|
|
6
|
-
RecordConditionOp,
|
|
7
|
-
RecordEnvelopeSchema,
|
|
8
|
-
RecordFilterSchema,
|
|
9
|
-
RecordListSchema,
|
|
10
|
-
RecordSortDirection,
|
|
11
|
-
type FindRecordsRequest,
|
|
12
|
-
} from "@stigmer/protos/ai/stigmer/agentic/datastore/v1/record_io_pb";
|
|
13
|
-
import { StigmerContext } from "../../context";
|
|
14
|
-
import { FetchCacheContext } from "../../internal/FetchCacheProvider";
|
|
15
|
-
import { useRecordCollection, type RecordColumnDef } from "../useRecordCollection";
|
|
16
|
-
|
|
17
|
-
function envelope(id: string, fields: Record<string, unknown> = {}) {
|
|
18
|
-
return create(RecordEnvelopeSchema, { id, fields: fields as never });
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
function recordList(records: ReturnType<typeof envelope>[], total: number) {
|
|
22
|
-
return create(RecordListSchema, { records, total, limit: 25, offset: 0 });
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
function createMockStigmer(findRecords: (req: FindRecordsRequest) => Promise<unknown>) {
|
|
26
|
-
return { datastore: { findRecords } } as never;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function wrapper(client: unknown) {
|
|
30
|
-
return function Wrapper({ children }: { children: ReactNode }) {
|
|
31
|
-
return (
|
|
32
|
-
<FetchCacheContext.Provider value={null}>
|
|
33
|
-
<StigmerContext.Provider value={client as never}>
|
|
34
|
-
{children}
|
|
35
|
-
</StigmerContext.Provider>
|
|
36
|
-
</FetchCacheContext.Provider>
|
|
37
|
-
);
|
|
38
|
-
};
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
const COLUMNS: readonly RecordColumnDef[] = [
|
|
42
|
-
{ id: "slot_date", header: "Date", cell: (r) => String(r.fields?.["slot_date"] ?? "") },
|
|
43
|
-
{ id: "status", header: "Status", cell: (r) => String(r.fields?.["status"] ?? ""), sortable: true },
|
|
44
|
-
];
|
|
45
|
-
|
|
46
|
-
const SCOPE = { org: "acme", datastore: "clinic", collection: "bookings" } as const;
|
|
47
|
-
|
|
48
|
-
describe("useRecordCollection", () => {
|
|
49
|
-
beforeEach(() => {
|
|
50
|
-
vi.restoreAllMocks();
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
it("fetches records and reports totals", async () => {
|
|
54
|
-
const findRecords = vi
|
|
55
|
-
.fn()
|
|
56
|
-
.mockResolvedValue(recordList([envelope("dsr_1"), envelope("dsr_2")], 51));
|
|
57
|
-
const { result } = renderHook(
|
|
58
|
-
() => useRecordCollection({ ...SCOPE, columns: COLUMNS }),
|
|
59
|
-
{ wrapper: wrapper(createMockStigmer(findRecords)) },
|
|
60
|
-
);
|
|
61
|
-
|
|
62
|
-
await waitFor(() => expect(result.current.isLoading).toBe(false));
|
|
63
|
-
|
|
64
|
-
expect(result.current.records).toHaveLength(2);
|
|
65
|
-
expect(result.current.total).toBe(51);
|
|
66
|
-
expect(result.current.totalPages).toBe(3); // 51 records at page size 25
|
|
67
|
-
expect(result.current.currentPage).toBe(1);
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
it("translates page/pageSize into RPC limit/offset", async () => {
|
|
71
|
-
const findRecords = vi.fn().mockResolvedValue(recordList([], 0));
|
|
72
|
-
renderHook(
|
|
73
|
-
() => useRecordCollection({ ...SCOPE, page: 3, pageSize: 10, columns: COLUMNS }),
|
|
74
|
-
{ wrapper: wrapper(createMockStigmer(findRecords)) },
|
|
75
|
-
);
|
|
76
|
-
|
|
77
|
-
await waitFor(() => expect(findRecords).toHaveBeenCalled());
|
|
78
|
-
expect(findRecords).toHaveBeenCalledWith(
|
|
79
|
-
expect.objectContaining({ limit: 10, offset: 20 }),
|
|
80
|
-
);
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
it("caps the page size at the RPC max of 100 — one surface, one limit", async () => {
|
|
84
|
-
const findRecords = vi.fn().mockResolvedValue(recordList([], 0));
|
|
85
|
-
renderHook(
|
|
86
|
-
() => useRecordCollection({ ...SCOPE, pageSize: 500, columns: COLUMNS }),
|
|
87
|
-
{ wrapper: wrapper(createMockStigmer(findRecords)) },
|
|
88
|
-
);
|
|
89
|
-
|
|
90
|
-
await waitFor(() => expect(findRecords).toHaveBeenCalled());
|
|
91
|
-
expect(findRecords).toHaveBeenCalledWith(expect.objectContaining({ limit: 100 }));
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
it("threads the partition into every fetch (DD-010)", async () => {
|
|
95
|
-
const findRecords = vi.fn().mockResolvedValue(recordList([], 0));
|
|
96
|
-
renderHook(
|
|
97
|
-
() => useRecordCollection({ ...SCOPE, partition: "dr-alt", columns: COLUMNS }),
|
|
98
|
-
{ wrapper: wrapper(createMockStigmer(findRecords)) },
|
|
99
|
-
);
|
|
100
|
-
|
|
101
|
-
await waitFor(() => expect(findRecords).toHaveBeenCalled());
|
|
102
|
-
expect(findRecords).toHaveBeenCalledWith(
|
|
103
|
-
expect.objectContaining({ partition: "dr-alt" }),
|
|
104
|
-
);
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
it("passes the typed proto filter through without adding grammar", async () => {
|
|
108
|
-
const filter = create(RecordFilterSchema, {
|
|
109
|
-
conditions: [
|
|
110
|
-
{ field: "status", op: RecordConditionOp.eq, value: { kind: { case: "stringValue", value: "confirmed" } } },
|
|
111
|
-
],
|
|
112
|
-
});
|
|
113
|
-
const findRecords = vi.fn().mockResolvedValue(recordList([], 0));
|
|
114
|
-
renderHook(
|
|
115
|
-
() => useRecordCollection({ ...SCOPE, filter, columns: COLUMNS }),
|
|
116
|
-
{ wrapper: wrapper(createMockStigmer(findRecords)) },
|
|
117
|
-
);
|
|
118
|
-
|
|
119
|
-
await waitFor(() => expect(findRecords).toHaveBeenCalled());
|
|
120
|
-
const request = findRecords.mock.calls[0][0] as FindRecordsRequest;
|
|
121
|
-
expect(request.filter?.conditions).toHaveLength(1);
|
|
122
|
-
expect(request.filter?.conditions[0].field).toBe("status");
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
it("builds RecordOrderBy from the controlled sort", async () => {
|
|
126
|
-
const findRecords = vi.fn().mockResolvedValue(recordList([], 0));
|
|
127
|
-
renderHook(
|
|
128
|
-
() =>
|
|
129
|
-
useRecordCollection({
|
|
130
|
-
...SCOPE,
|
|
131
|
-
sort: { id: "slot_date", direction: "desc" },
|
|
132
|
-
columns: COLUMNS,
|
|
133
|
-
}),
|
|
134
|
-
{ wrapper: wrapper(createMockStigmer(findRecords)) },
|
|
135
|
-
);
|
|
136
|
-
|
|
137
|
-
await waitFor(() => expect(findRecords).toHaveBeenCalled());
|
|
138
|
-
const request = findRecords.mock.calls[0][0] as FindRecordsRequest;
|
|
139
|
-
expect(request.orderBy?.field).toBe("slot_date");
|
|
140
|
-
expect(request.orderBy?.direction).toBe(RecordSortDirection.desc);
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
it("reports header sort clicks through onSortChange", async () => {
|
|
144
|
-
const findRecords = vi.fn().mockResolvedValue(recordList([envelope("dsr_1")], 1));
|
|
145
|
-
const onSortChange = vi.fn();
|
|
146
|
-
const { result } = renderHook(
|
|
147
|
-
() => useRecordCollection({ ...SCOPE, sort: null, onSortChange, columns: COLUMNS }),
|
|
148
|
-
{ wrapper: wrapper(createMockStigmer(findRecords)) },
|
|
149
|
-
);
|
|
150
|
-
|
|
151
|
-
await waitFor(() => expect(result.current.isLoading).toBe(false));
|
|
152
|
-
|
|
153
|
-
const statusColumn = result.current.table!
|
|
154
|
-
.getHeaderGroups()[0]
|
|
155
|
-
.headers.find((h) => h.id === "status")!;
|
|
156
|
-
act(() => statusColumn.column.toggleSorting(false));
|
|
157
|
-
|
|
158
|
-
expect(onSortChange).toHaveBeenCalledWith({ id: "status", direction: "asc" });
|
|
159
|
-
});
|
|
160
|
-
|
|
161
|
-
it("produces a table keyed by envelope id", async () => {
|
|
162
|
-
const findRecords = vi
|
|
163
|
-
.fn()
|
|
164
|
-
.mockResolvedValue(recordList([envelope("dsr_a"), envelope("dsr_b")], 2));
|
|
165
|
-
const { result } = renderHook(
|
|
166
|
-
() => useRecordCollection({ ...SCOPE, columns: COLUMNS }),
|
|
167
|
-
{ wrapper: wrapper(createMockStigmer(findRecords)) },
|
|
168
|
-
);
|
|
169
|
-
|
|
170
|
-
await waitFor(() => expect(result.current.isLoading).toBe(false));
|
|
171
|
-
expect(result.current.table!.getRowModel().rows.map((r) => r.id)).toEqual([
|
|
172
|
-
"dsr_a",
|
|
173
|
-
"dsr_b",
|
|
174
|
-
]);
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
it("returns a null table when no columns are provided", async () => {
|
|
178
|
-
const findRecords = vi.fn().mockResolvedValue(recordList([], 0));
|
|
179
|
-
const { result } = renderHook(() => useRecordCollection({ ...SCOPE }), {
|
|
180
|
-
wrapper: wrapper(createMockStigmer(findRecords)),
|
|
181
|
-
});
|
|
182
|
-
|
|
183
|
-
await waitFor(() => expect(result.current.isLoading).toBe(false));
|
|
184
|
-
expect(result.current.table).toBeNull();
|
|
185
|
-
});
|
|
186
|
-
|
|
187
|
-
it("skips fetching when the scope is incomplete", () => {
|
|
188
|
-
const findRecords = vi.fn();
|
|
189
|
-
const { result } = renderHook(
|
|
190
|
-
() =>
|
|
191
|
-
useRecordCollection({
|
|
192
|
-
org: "acme",
|
|
193
|
-
datastore: "clinic",
|
|
194
|
-
collection: null,
|
|
195
|
-
columns: COLUMNS,
|
|
196
|
-
}),
|
|
197
|
-
{ wrapper: wrapper(createMockStigmer(findRecords)) },
|
|
198
|
-
);
|
|
199
|
-
|
|
200
|
-
expect(result.current.isLoading).toBe(false);
|
|
201
|
-
expect(findRecords).not.toHaveBeenCalled();
|
|
202
|
-
});
|
|
203
|
-
|
|
204
|
-
it("surfaces the record-layer denial verbatim — never a silently empty grid", async () => {
|
|
205
|
-
const denial = new Error("you are not allowed to read records in bookings");
|
|
206
|
-
const findRecords = vi.fn().mockRejectedValue(denial);
|
|
207
|
-
const { result } = renderHook(
|
|
208
|
-
() => useRecordCollection({ ...SCOPE, columns: COLUMNS }),
|
|
209
|
-
{ wrapper: wrapper(createMockStigmer(findRecords)) },
|
|
210
|
-
);
|
|
211
|
-
|
|
212
|
-
await waitFor(() => expect(result.current.isLoading).toBe(false));
|
|
213
|
-
expect(result.current.error?.message).toBe(
|
|
214
|
-
"you are not allowed to read records in bookings",
|
|
215
|
-
);
|
|
216
|
-
});
|
|
217
|
-
|
|
218
|
-
it("refetch re-runs the current query", async () => {
|
|
219
|
-
const findRecords = vi.fn().mockResolvedValue(recordList([envelope("dsr_1")], 1));
|
|
220
|
-
const { result } = renderHook(
|
|
221
|
-
() => useRecordCollection({ ...SCOPE, columns: COLUMNS }),
|
|
222
|
-
{ wrapper: wrapper(createMockStigmer(findRecords)) },
|
|
223
|
-
);
|
|
224
|
-
|
|
225
|
-
await waitFor(() => expect(result.current.isLoading).toBe(false));
|
|
226
|
-
expect(findRecords).toHaveBeenCalledTimes(1);
|
|
227
|
-
|
|
228
|
-
act(() => result.current.refetch());
|
|
229
|
-
await waitFor(() => expect(findRecords).toHaveBeenCalledTimes(2));
|
|
230
|
-
});
|
|
231
|
-
|
|
232
|
-
it("re-fetches when the filter changes", async () => {
|
|
233
|
-
const findRecords = vi.fn().mockResolvedValue(recordList([], 0));
|
|
234
|
-
const { rerender } = renderHook(
|
|
235
|
-
({ filter }: { filter?: ReturnType<typeof create<typeof RecordFilterSchema>> }) =>
|
|
236
|
-
useRecordCollection({ ...SCOPE, filter, columns: COLUMNS }),
|
|
237
|
-
{ wrapper: wrapper(createMockStigmer(findRecords)), initialProps: {} },
|
|
238
|
-
);
|
|
239
|
-
|
|
240
|
-
await waitFor(() => expect(findRecords).toHaveBeenCalledTimes(1));
|
|
241
|
-
|
|
242
|
-
rerender({
|
|
243
|
-
filter: create(RecordFilterSchema, {
|
|
244
|
-
conditions: [{ field: "status", op: RecordConditionOp.is_null }],
|
|
245
|
-
}),
|
|
246
|
-
});
|
|
247
|
-
|
|
248
|
-
await waitFor(() => expect(findRecords).toHaveBeenCalledTimes(2));
|
|
249
|
-
});
|
|
250
|
-
});
|
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
2
|
-
import { renderHook, waitFor } from "@testing-library/react";
|
|
3
|
-
import type { ReactNode } from "react";
|
|
4
|
-
import { create } from "@bufbuild/protobuf";
|
|
5
|
-
import {
|
|
6
|
-
RecordEnvelopeSchema,
|
|
7
|
-
RecordListSchema,
|
|
8
|
-
} from "@stigmer/protos/ai/stigmer/agentic/datastore/v1/record_io_pb";
|
|
9
|
-
import { StigmerContext } from "../../context";
|
|
10
|
-
import { FetchCacheContext } from "../../internal/FetchCacheProvider";
|
|
11
|
-
import { useRecordList } from "../useRecordList";
|
|
12
|
-
|
|
13
|
-
function wrapper(client: unknown) {
|
|
14
|
-
return function Wrapper({ children }: { children: ReactNode }) {
|
|
15
|
-
return (
|
|
16
|
-
<FetchCacheContext.Provider value={null}>
|
|
17
|
-
<StigmerContext.Provider value={client as never}>
|
|
18
|
-
{children}
|
|
19
|
-
</StigmerContext.Provider>
|
|
20
|
-
</FetchCacheContext.Provider>
|
|
21
|
-
);
|
|
22
|
-
};
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
const SCOPE = { org: "acme", datastore: "clinic", collection: "bookings" } as const;
|
|
26
|
-
|
|
27
|
-
describe("useRecordList", () => {
|
|
28
|
-
beforeEach(() => vi.restoreAllMocks());
|
|
29
|
-
|
|
30
|
-
it("fetches a page of records", async () => {
|
|
31
|
-
const list = create(RecordListSchema, {
|
|
32
|
-
records: [create(RecordEnvelopeSchema, { id: "dsr_1" })],
|
|
33
|
-
total: 1,
|
|
34
|
-
});
|
|
35
|
-
const findRecords = vi.fn().mockResolvedValue(list);
|
|
36
|
-
const client = { datastore: { findRecords } } as never;
|
|
37
|
-
|
|
38
|
-
const { result } = renderHook(() => useRecordList({ ...SCOPE }), {
|
|
39
|
-
wrapper: wrapper(client),
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
await waitFor(() => expect(result.current.isLoading).toBe(false));
|
|
43
|
-
expect(result.current.records).toHaveLength(1);
|
|
44
|
-
expect(result.current.total).toBe(1);
|
|
45
|
-
expect(findRecords).toHaveBeenCalledWith(
|
|
46
|
-
expect.objectContaining({
|
|
47
|
-
org: "acme",
|
|
48
|
-
datastore: "clinic",
|
|
49
|
-
collection: "bookings",
|
|
50
|
-
partition: "",
|
|
51
|
-
limit: 0, // unset — the server applies its default of 25
|
|
52
|
-
offset: 0,
|
|
53
|
-
}),
|
|
54
|
-
);
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
it("skips fetching when params is null", () => {
|
|
58
|
-
const findRecords = vi.fn();
|
|
59
|
-
const client = { datastore: { findRecords } } as never;
|
|
60
|
-
|
|
61
|
-
const { result } = renderHook(() => useRecordList(null), {
|
|
62
|
-
wrapper: wrapper(client),
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
expect(result.current.isLoading).toBe(false);
|
|
66
|
-
expect(result.current.records).toEqual([]);
|
|
67
|
-
expect(findRecords).not.toHaveBeenCalled();
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
it("surfaces the record-layer denial verbatim", async () => {
|
|
71
|
-
const denial = new Error("you are not allowed to read records in bookings");
|
|
72
|
-
const client = {
|
|
73
|
-
datastore: { findRecords: vi.fn().mockRejectedValue(denial) },
|
|
74
|
-
} as never;
|
|
75
|
-
|
|
76
|
-
const { result } = renderHook(() => useRecordList({ ...SCOPE }), {
|
|
77
|
-
wrapper: wrapper(client),
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
await waitFor(() => expect(result.current.isLoading).toBe(false));
|
|
81
|
-
expect(result.current.error?.message).toBe(
|
|
82
|
-
"you are not allowed to read records in bookings",
|
|
83
|
-
);
|
|
84
|
-
});
|
|
85
|
-
});
|
package/src/datastore/index.ts
DELETED
|
@@ -1,111 +0,0 @@
|
|
|
1
|
-
export { useDatastore } from "./useDatastore.js";
|
|
2
|
-
export type { UseDatastoreReturn } from "./useDatastore.js";
|
|
3
|
-
|
|
4
|
-
export { useDatastoreList } from "./useDatastoreList.js";
|
|
5
|
-
export type {
|
|
6
|
-
UseDatastoreListOptions,
|
|
7
|
-
UseDatastoreListReturn,
|
|
8
|
-
} from "./useDatastoreList.js";
|
|
9
|
-
|
|
10
|
-
export { useDatastoreCount } from "./useDatastoreCount.js";
|
|
11
|
-
export type {
|
|
12
|
-
UseDatastoreCountOptions,
|
|
13
|
-
UseDatastoreCountReturn,
|
|
14
|
-
} from "./useDatastoreCount.js";
|
|
15
|
-
|
|
16
|
-
export { useDatastoreSearch } from "./useDatastoreSearch.js";
|
|
17
|
-
export type {
|
|
18
|
-
UseDatastoreSearchOptions,
|
|
19
|
-
UseDatastoreSearchReturn,
|
|
20
|
-
} from "./useDatastoreSearch.js";
|
|
21
|
-
|
|
22
|
-
export { useDatastoreDescription, DEFAULT_PARTITION } from "./useDatastoreDescription.js";
|
|
23
|
-
export type { UseDatastoreDescriptionReturn } from "./useDatastoreDescription.js";
|
|
24
|
-
|
|
25
|
-
export { useRecordList } from "./useRecordList.js";
|
|
26
|
-
export type {
|
|
27
|
-
RecordScope,
|
|
28
|
-
UseRecordListParams,
|
|
29
|
-
UseRecordListReturn,
|
|
30
|
-
} from "./useRecordList.js";
|
|
31
|
-
|
|
32
|
-
export { useInsertRecord } from "./useInsertRecord.js";
|
|
33
|
-
export type {
|
|
34
|
-
InsertRecordArgs,
|
|
35
|
-
UseInsertRecordReturn,
|
|
36
|
-
} from "./useInsertRecord.js";
|
|
37
|
-
|
|
38
|
-
export { useUpdateRecord } from "./useUpdateRecord.js";
|
|
39
|
-
export type {
|
|
40
|
-
UpdateRecordArgs,
|
|
41
|
-
UseUpdateRecordReturn,
|
|
42
|
-
} from "./useUpdateRecord.js";
|
|
43
|
-
|
|
44
|
-
export { useDeleteRecord } from "./useDeleteRecord.js";
|
|
45
|
-
export type {
|
|
46
|
-
DeleteRecordArgs,
|
|
47
|
-
UseDeleteRecordReturn,
|
|
48
|
-
} from "./useDeleteRecord.js";
|
|
49
|
-
|
|
50
|
-
export { useRecordCollection } from "./useRecordCollection.js";
|
|
51
|
-
export type {
|
|
52
|
-
RecordColumnDef,
|
|
53
|
-
UseRecordCollectionOptions,
|
|
54
|
-
UseRecordCollectionReturn,
|
|
55
|
-
} from "./useRecordCollection.js";
|
|
56
|
-
|
|
57
|
-
export { useDeleteDatastore } from "./useDeleteDatastore.js";
|
|
58
|
-
export type {
|
|
59
|
-
DeleteDatastoreArgs,
|
|
60
|
-
UseDeleteDatastoreReturn,
|
|
61
|
-
} from "./useDeleteDatastore.js";
|
|
62
|
-
|
|
63
|
-
export {
|
|
64
|
-
buildUpdateFields,
|
|
65
|
-
coerceFieldValue,
|
|
66
|
-
formatFieldValue,
|
|
67
|
-
formatSystemTimestamp,
|
|
68
|
-
isListOperator,
|
|
69
|
-
isSortableField,
|
|
70
|
-
isValuelessOperator,
|
|
71
|
-
operatorsForField,
|
|
72
|
-
OPERATOR_LABELS,
|
|
73
|
-
RESERVED_FIELD_NAMES,
|
|
74
|
-
SYSTEM_FIELD_OPERATORS,
|
|
75
|
-
} from "./recordValues.js";
|
|
76
|
-
export type { CoerceResult } from "./recordValues.js";
|
|
77
|
-
|
|
78
|
-
export {
|
|
79
|
-
buildRecordFilter,
|
|
80
|
-
filterableFields,
|
|
81
|
-
formatConditionChip,
|
|
82
|
-
isConditionComplete,
|
|
83
|
-
} from "./recordFilter.js";
|
|
84
|
-
export type { FilterableField, RecordConditionDraft } from "./recordFilter.js";
|
|
85
|
-
|
|
86
|
-
export { FieldValueControl, FIELD_INPUT_CLASSES } from "./FieldValueControl.js";
|
|
87
|
-
export type { FieldValueControlProps } from "./FieldValueControl.js";
|
|
88
|
-
|
|
89
|
-
export { CollectionSchemaView, formatSubject } from "./CollectionSchemaView.js";
|
|
90
|
-
export type { CollectionSchemaViewProps } from "./CollectionSchemaView.js";
|
|
91
|
-
|
|
92
|
-
export { DatastoreSyncReport } from "./DatastoreSyncReport.js";
|
|
93
|
-
export type { DatastoreSyncReportProps } from "./DatastoreSyncReport.js";
|
|
94
|
-
|
|
95
|
-
export { RecordFilterBuilder } from "./RecordFilterBuilder.js";
|
|
96
|
-
export type { RecordFilterBuilderProps } from "./RecordFilterBuilder.js";
|
|
97
|
-
|
|
98
|
-
export { RecordFormPanel } from "./RecordFormPanel.js";
|
|
99
|
-
export type { RecordFormPanelProps } from "./RecordFormPanel.js";
|
|
100
|
-
|
|
101
|
-
export { CollectionRecordsBrowser } from "./CollectionRecordsBrowser.js";
|
|
102
|
-
export type { CollectionRecordsBrowserProps } from "./CollectionRecordsBrowser.js";
|
|
103
|
-
|
|
104
|
-
export { DeleteDatastoreDialog } from "./DeleteDatastoreDialog.js";
|
|
105
|
-
export type { DeleteDatastoreDialogProps } from "./DeleteDatastoreDialog.js";
|
|
106
|
-
|
|
107
|
-
export { DatastoreDetailView, DatastoreIcon } from "./DatastoreDetailView.js";
|
|
108
|
-
export type {
|
|
109
|
-
DatastoreDetailTab,
|
|
110
|
-
DatastoreDetailViewProps,
|
|
111
|
-
} from "./DatastoreDetailView.js";
|
|
@@ -1,154 +0,0 @@
|
|
|
1
|
-
// The records-specific filter model (DD-008 SD-3) — typed 1:1 against
|
|
2
|
-
// `RecordConditionOp`.
|
|
3
|
-
//
|
|
4
|
-
// Deliberately NOT the workbench `FilterDef`/`FilterValue` model: its
|
|
5
|
-
// `string | string[]` value shape cannot carry typed values or
|
|
6
|
-
// value-less null operators, and its operator set includes `contains`,
|
|
7
|
-
// which `findRecords` cannot serve. Welding a server-grammar contract
|
|
8
|
-
// onto a client-filter model would couple two contracts that evolve
|
|
9
|
-
// independently — so this model mirrors the RPC grammar exactly and can
|
|
10
|
-
// express nothing the RPC cannot serve (DD-008 invariant 2).
|
|
11
|
-
|
|
12
|
-
import { create, fromJson, type JsonValue } from "@bufbuild/protobuf";
|
|
13
|
-
import { ValueSchema } from "@bufbuild/protobuf/wkt";
|
|
14
|
-
import {
|
|
15
|
-
RecordConditionOp,
|
|
16
|
-
RecordConditionSchema,
|
|
17
|
-
RecordFilterSchema,
|
|
18
|
-
type RecordFilter,
|
|
19
|
-
} from "@stigmer/protos/ai/stigmer/agentic/datastore/v1/record_io_pb";
|
|
20
|
-
import {
|
|
21
|
-
FieldType,
|
|
22
|
-
type CollectionDeclaration,
|
|
23
|
-
type FieldDeclaration,
|
|
24
|
-
} from "@stigmer/protos/ai/stigmer/agentic/datastore/v1/spec_pb";
|
|
25
|
-
import {
|
|
26
|
-
isListOperator,
|
|
27
|
-
isValuelessOperator,
|
|
28
|
-
OPERATOR_LABELS,
|
|
29
|
-
SYSTEM_FIELD_OPERATORS,
|
|
30
|
-
operatorsForField,
|
|
31
|
-
} from "./recordValues.js";
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* One draft condition in the filter builder — the client-side twin of
|
|
35
|
-
* the proto `RecordCondition`, with plain JSON values (converted to
|
|
36
|
-
* `google.protobuf.Value` only at request-build time).
|
|
37
|
-
*/
|
|
38
|
-
export interface RecordConditionDraft {
|
|
39
|
-
/** Declared field name, or a filterable system field (`id`, `created_at`, `updated_at`). */
|
|
40
|
-
readonly field: string;
|
|
41
|
-
/** Comparison operator — must be admitted by the field's operator set. */
|
|
42
|
-
readonly op: RecordConditionOp;
|
|
43
|
-
/** Scalar comparison value. Absent for `is_null`/`not_null` and list operators. */
|
|
44
|
-
readonly value?: JsonValue;
|
|
45
|
-
/** Comparison values for `is_in`/`not_in`. */
|
|
46
|
-
readonly values?: readonly JsonValue[];
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* A field the builder can offer: a declared filterable field or a
|
|
51
|
-
* filterable system field, with the exact operator set the server
|
|
52
|
-
* admits for it.
|
|
53
|
-
*/
|
|
54
|
-
export interface FilterableField {
|
|
55
|
-
readonly name: string;
|
|
56
|
-
/** The declaration, or `undefined` for system fields. */
|
|
57
|
-
readonly declaration?: FieldDeclaration;
|
|
58
|
-
/** The field type driving the value control (system fields use fixed types). */
|
|
59
|
-
readonly type: FieldType;
|
|
60
|
-
readonly operators: readonly RecordConditionOp[];
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* The fields a collection's filter builder offers: declared filterable
|
|
65
|
-
* fields (json is not filterable) followed by the filterable system
|
|
66
|
-
* fields. `id` conditions take string values; the audit timestamps take
|
|
67
|
-
* RFC 3339 timestamps — both ride the `timestamp`/`string` value
|
|
68
|
-
* controls.
|
|
69
|
-
*
|
|
70
|
-
* `readableFields` is the caller's column-level read access from
|
|
71
|
-
* `describeDatastore` (the read verb's `readable_fields`; empty means
|
|
72
|
-
* every field). When restricted, unreadable declared fields are not
|
|
73
|
-
* offered — the server refuses conditions on them (the existence-oracle
|
|
74
|
-
* guard), and the builder must stay structurally unable to express an
|
|
75
|
-
* unservable filter (DD-008 invariant 2). System fields are always
|
|
76
|
-
* readable and always offered.
|
|
77
|
-
*/
|
|
78
|
-
export function filterableFields(
|
|
79
|
-
collection: CollectionDeclaration,
|
|
80
|
-
readableFields?: readonly string[],
|
|
81
|
-
): FilterableField[] {
|
|
82
|
-
const readable =
|
|
83
|
-
readableFields !== undefined && readableFields.length > 0 ? new Set(readableFields) : null;
|
|
84
|
-
const declared: FilterableField[] = [];
|
|
85
|
-
for (const field of collection.fields) {
|
|
86
|
-
const operators = operatorsForField(field);
|
|
87
|
-
if (operators.length === 0) continue; // json — not filterable
|
|
88
|
-
if (readable !== null && !readable.has(field.name)) continue; // not readable under the grant
|
|
89
|
-
declared.push({ name: field.name, declaration: field, type: field.type, operators });
|
|
90
|
-
}
|
|
91
|
-
return [
|
|
92
|
-
...declared,
|
|
93
|
-
{ name: "id", type: FieldType.string, operators: SYSTEM_FIELD_OPERATORS.get("id")! },
|
|
94
|
-
{
|
|
95
|
-
name: "created_at",
|
|
96
|
-
type: FieldType.timestamp,
|
|
97
|
-
operators: SYSTEM_FIELD_OPERATORS.get("created_at")!,
|
|
98
|
-
},
|
|
99
|
-
{
|
|
100
|
-
name: "updated_at",
|
|
101
|
-
type: FieldType.timestamp,
|
|
102
|
-
operators: SYSTEM_FIELD_OPERATORS.get("updated_at")!,
|
|
103
|
-
},
|
|
104
|
-
];
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
/**
|
|
108
|
-
* Whether a draft is complete enough to serve: value-less operators
|
|
109
|
-
* need nothing, list operators need at least one value, scalar
|
|
110
|
-
* operators need a value.
|
|
111
|
-
*/
|
|
112
|
-
export function isConditionComplete(draft: RecordConditionDraft): boolean {
|
|
113
|
-
if (draft.op === RecordConditionOp.record_condition_op_unspecified) return false;
|
|
114
|
-
if (isValuelessOperator(draft.op)) return true;
|
|
115
|
-
if (isListOperator(draft.op)) return (draft.values?.length ?? 0) > 0;
|
|
116
|
-
return draft.value !== undefined;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
/**
|
|
120
|
-
* Build the proto `RecordFilter` from complete drafts. Returns
|
|
121
|
-
* `undefined` for an empty set (an absent filter, not an empty one, is
|
|
122
|
-
* the RPC's "no filter").
|
|
123
|
-
*/
|
|
124
|
-
export function buildRecordFilter(
|
|
125
|
-
conditions: readonly RecordConditionDraft[],
|
|
126
|
-
): RecordFilter | undefined {
|
|
127
|
-
const complete = conditions.filter(isConditionComplete);
|
|
128
|
-
if (complete.length === 0) return undefined;
|
|
129
|
-
return create(RecordFilterSchema, {
|
|
130
|
-
conditions: complete.map((draft) =>
|
|
131
|
-
create(RecordConditionSchema, {
|
|
132
|
-
field: draft.field,
|
|
133
|
-
op: draft.op,
|
|
134
|
-
value: draft.value !== undefined ? fromJson(ValueSchema, draft.value) : undefined,
|
|
135
|
-
values: (draft.values ?? []).map((v) => fromJson(ValueSchema, v)),
|
|
136
|
-
}),
|
|
137
|
-
),
|
|
138
|
-
});
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
/** Chip text for an active condition, e.g. `status = confirmed` or `notes is empty`. */
|
|
142
|
-
export function formatConditionChip(draft: RecordConditionDraft): string {
|
|
143
|
-
const op = OPERATOR_LABELS.get(draft.op) ?? "?";
|
|
144
|
-
if (isValuelessOperator(draft.op)) return `${draft.field} ${op}`;
|
|
145
|
-
if (isListOperator(draft.op)) {
|
|
146
|
-
return `${draft.field} ${op} [${(draft.values ?? []).map(formatChipValue).join(", ")}]`;
|
|
147
|
-
}
|
|
148
|
-
return `${draft.field} ${op} ${formatChipValue(draft.value)}`;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
function formatChipValue(value: JsonValue | undefined): string {
|
|
152
|
-
if (value === undefined || value === null) return "";
|
|
153
|
-
return typeof value === "string" ? value : JSON.stringify(value);
|
|
154
|
-
}
|