@agent-native/core 0.133.2 → 0.133.3

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.
Files changed (58) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +6 -0
  3. package/corpus/core/package.json +1 -1
  4. package/corpus/core/src/client/use-action.ts +6 -0
  5. package/corpus/templates/content/actions/_batch-utils.ts +33 -0
  6. package/corpus/templates/content/actions/_builder-cms-read-client.ts +173 -81
  7. package/corpus/templates/content/actions/_content-files.ts +31 -21
  8. package/corpus/templates/content/actions/_database-source-utils.ts +653 -155
  9. package/corpus/templates/content/actions/_database-utils.ts +535 -41
  10. package/corpus/templates/content/actions/add-content-database-source-field-property.ts +291 -240
  11. package/corpus/templates/content/actions/add-database-item.ts +15 -1
  12. package/corpus/templates/content/actions/attach-content-database-source.ts +156 -58
  13. package/corpus/templates/content/actions/bind-content-database-source-field.ts +2 -2
  14. package/corpus/templates/content/actions/cancel-prepared-builder-source-update.ts +4 -1
  15. package/corpus/templates/content/actions/delete-database-items.ts +8 -2
  16. package/corpus/templates/content/actions/disconnect-content-database-source.ts +2 -2
  17. package/corpus/templates/content/actions/duplicate-database-item.ts +15 -1
  18. package/corpus/templates/content/actions/duplicate-database-items.ts +19 -1
  19. package/corpus/templates/content/actions/execute-builder-source-execution.ts +2 -1
  20. package/corpus/templates/content/actions/get-content-database.ts +12 -64
  21. package/corpus/templates/content/actions/move-database-item.ts +4 -1
  22. package/corpus/templates/content/actions/prepare-builder-source-execution.ts +1 -1
  23. package/corpus/templates/content/actions/prepare-builder-source-review.ts +4 -8
  24. package/corpus/templates/content/actions/preview-content-database-source-attach.ts +92 -0
  25. package/corpus/templates/content/actions/process-builder-body-hydration.ts +2 -1
  26. package/corpus/templates/content/actions/query-content-database-items.ts +64 -0
  27. package/corpus/templates/content/actions/refresh-content-database-source.ts +7 -0
  28. package/corpus/templates/content/actions/review-content-database-source-change-set.ts +1 -1
  29. package/corpus/templates/content/actions/set-content-database-source-write-mode.ts +1 -1
  30. package/corpus/templates/content/actions/stage-builder-revision.ts +1 -1
  31. package/corpus/templates/content/actions/update-content-database-personal-view.ts +29 -3
  32. package/corpus/templates/content/actions/update-content-database-view.ts +1 -1
  33. package/corpus/templates/content/actions/validate-builder-source-execution.ts +1 -1
  34. package/corpus/templates/content/app/components/editor/DocumentProperties.tsx +3 -22
  35. package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +267 -105
  36. package/corpus/templates/content/app/components/sidebar/DocumentSidebar.tsx +82 -1
  37. package/corpus/templates/content/app/hooks/use-content-database.ts +285 -24
  38. package/corpus/templates/content/app/hooks/use-document-properties.ts +7 -6
  39. package/corpus/templates/content/app/i18n-data.ts +10 -0
  40. package/corpus/templates/content/changelog/2026-07-29-large-databases-keep-useful-rows-visible.md +6 -0
  41. package/corpus/templates/content/changelog/2026-07-30-builder-source-columns-now-appear-immediately-when-connected.md +6 -0
  42. package/corpus/templates/content/changelog/2026-07-30-large-builder-backed-tables-show-useful-rows-sooner-and-fini.md +6 -0
  43. package/corpus/templates/content/changelog/2026-08-01-large-builder-databases-now-show-rows-immediately-and-finish.md +6 -0
  44. package/corpus/templates/content/parity/matrix.md +2 -1
  45. package/corpus/templates/content/parity/matrix.ts +25 -0
  46. package/corpus/templates/content/shared/api.ts +54 -4
  47. package/corpus/templates/content/shared/database-query.ts +359 -0
  48. package/dist/client/use-action.d.ts.map +1 -1
  49. package/dist/client/use-action.js +6 -0
  50. package/dist/client/use-action.js.map +1 -1
  51. package/dist/notifications/routes.d.ts +1 -1
  52. package/dist/observability/routes.d.ts +3 -3
  53. package/dist/progress/routes.d.ts +1 -1
  54. package/dist/resources/handlers.d.ts +1 -1
  55. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  56. package/dist/server/transcribe-voice.d.ts +1 -1
  57. package/package.json +1 -1
  58. package/src/client/use-action.ts +6 -0
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: improved
3
+ date: 2026-08-01
4
+ ---
5
+
6
+ Large Builder databases now show rows immediately and finish syncing much faster.
@@ -10,6 +10,7 @@ This generated matrix tracks whether high-value Content UI operations use the sa
10
10
  | database.private-preview-drafts | database | Persist and reconcile a user's private database-page preview draft | action-backed | `get-preview-document-draft`, `update-preview-document-draft` | `app/components/editor/database/DatabaseView.tsx`, `app/hooks/use-documents.ts` | A user's private preview draft is read, saved, conflict-checked, or deleted without changing the shared database page until the normal save flow applies it. | These per-user editor-state actions are intentionally hidden from agent tools because preview drafts are a private UI recovery mechanism. | - | P1 | covered | `actions/preview-document-draft.db.test.ts` | - | - |
11
11
  | database.properties-and-view-config | database | Configure properties, values, ordering, and saved views | action-backed | `configure-document-property`, `delete-document-property`, `duplicate-document-property`, `get-content-database-personal-view`, `list-document-properties`, `reorder-document-property`, `set-document-property`, `update-content-database-personal-view`, `update-content-database-view` | `app/components/editor/DocumentProperties.tsx`, `app/components/editor/DocumentDatabase.tsx`, `app/components/editor/database/DatabaseView.tsx`, `app/hooks/use-document-properties.ts` | Property schemas, property values, property order, filters, sorts, grouping, hidden columns, view type, and view settings are stored. | - | - | P0 | covered | `actions/bind-content-database-source-field.db.test.ts`, `actions/content-database-source-actions.test.ts`, `actions/resync-content-database-source.db.test.ts` | `database-source-scope` | - |
12
12
  | database.rows | database | Add, duplicate, move, open, and delete database rows | action-backed | `add-database-item`, `delete-database-items`, `delete-document`, `duplicate-database-items`, `duplicate-database-item`, `move-database-item`, `set-document-property` | `app/components/editor/DocumentDatabase.tsx`, `app/components/editor/database/DatabaseView.tsx` | Database row backing documents and row ordering are created, duplicated, moved, edited, and deleted. | - | - | P0 | covered | `actions/database-row-batch-actions.db.test.ts`, `parity/__tests__/database-row-batch-reliability.test.ts` | `database-bulk-row-reliability` | - |
13
+ | database.table-query-page | database | Query one constrained page while retaining database metadata | action-backed | `query-content-database-items` | `app/components/editor/database/DatabaseView.tsx`, `app/hooks/use-content-database.ts` | - | This UI-only bounded projection is intentionally hidden with agentTool: false; agents use get-content-database for the complete database contract. | - | P0 | covered | `actions/content-database-lifecycle.db.test.ts`, `app/hooks/use-content-database.test.ts` | - | - |
13
14
  | editor.agent-assist-prompts | editor | Ask AI from slash generation or comment context | client-assist | - | `app/components/editor/SlashCommandMenu.tsx`, `app/components/editor/CommentsSidebar.tsx` | No direct durable mutation; the prompt asks the agent to use document actions when it decides to write. | - | - | P1 | none | - | - | - |
14
15
  | editor.client-formatting-and-insertions | editor | Rich text formatting, selection state, slash block insertion, and copy actions | client-only-ephemeral | - | `app/components/editor/BubbleToolbar.tsx`, `app/components/editor/SlashCommandMenu.tsx`, `app/components/editor/DocumentToolbar.tsx` | - | - | - | P1 | none | - | - | - |
15
16
  | editor.document-body-and-title | editor | Edit document title, body, icon, image alt text, and precise text | action-backed | `edit-document`, `pull-document`, `set-image-alt-text`, `transcribe-media`, `update-document` | `app/components/editor/DocumentEditor.tsx`, `app/components/editor/DocumentDatabase.tsx`, `app/components/editor/extensions/ImageBlock.tsx`, `app/components/editor/SlashCommandMenu.tsx` | Document content, title, icon, image metadata, and text replacements are saved to the same document source. | - | - | P0 | covered | `actions/content-database-lifecycle.db.test.ts`, `actions/_local-file-documents.test.ts` | `document-search-edit` | - |
@@ -26,7 +27,7 @@ This generated matrix tracks whether high-value Content UI operations use the sa
26
27
  | source-sync.builder-cms-review-and-write-gates | source-sync | Review, stage, validate, cancel, and execute Builder CMS source writes | action-backed | `cancel-prepared-builder-source-update`, `execute-builder-source-batch`, `execute-builder-source-execution`, `prepare-builder-source-execution`, `prepare-builder-source-review`, `preview-builder-source-review`, `review-content-database-source-change-set`, `set-content-database-source-write-mode`, `stage-builder-source-bulk-update`, `stage-builder-revision`, `validate-builder-source-execution` | `app/components/editor/DocumentDatabase.tsx`, `app/components/editor/database/DatabaseView.tsx`, `app/components/editor/database-sources/BuilderSourceReviewDialog.tsx` | Builder source write mode, staged reviews, pre-dispatch cancellations, validation records, and bounded execution records are created through guarded actions. | - | - | P0 | covered | `actions/builder-source-review-gates.db.test.ts`, `actions/cancel-prepared-builder-source-update.db.test.ts`, `actions/execute-builder-source-execution.test.ts`, `actions/stage-builder-source-bulk-update.db.test.ts` | `builder-source-review-readonly` | - |
27
28
  | source-sync.builder-documents | source-sync | List, pull, check, and push Builder docs/blog MDX documents | action-backed | `check-builder-doc`, `list-builder-docs`, `pull-builder-doc`, `push-builder-doc` | `actions/list-builder-docs.ts`, `actions/pull-builder-doc.ts`, `actions/check-builder-doc.ts`, `actions/push-builder-doc.ts` | Builder docs/blog entries can be read into Content, checked locally, and pushed through guarded Builder document actions. | - | - | P1 | seeded | - | - | - |
28
29
  | source-sync.builder-required-field-materialization | source-sync | Add required Builder publishing fields to a connected collection | action-backed | `materialize-builder-required-fields` | `app/components/editor/database/DatabaseView.tsx`, `app/hooks/use-content-database.ts` | Required Builder fields are materialized as editable Content properties in one local mutation. | This bounded safe-model setup action is intentionally hidden from the agent tool list; the visible source settings surface invokes it. | - | P1 | covered | `actions/materialize-builder-required-fields.test.ts` | - | - |
29
- | source-sync.database-source-bindings | source-sync | Attach, inspect, refresh, disconnect, join, and bind database sources | action-backed | `add-content-database-source-field-property`, `attach-content-database-source`, `bind-content-database-source-field`, `change-content-database-source-role`, `disconnect-content-database-source`, `get-content-database-source`, `list-builder-cms-models`, `list-notion-database-sources`, `refresh-content-database-source`, `suggest-source-join-key` | `app/components/editor/DocumentDatabase.tsx`, `app/components/editor/DocumentProperties.tsx`, `app/components/editor/database/DatabaseView.tsx`, `app/hooks/use-content-database.ts` | Mounted database source metadata, fields, source role, join keys, and source-field/property bindings are stored and refreshed. | - | - | P0 | covered | `actions/bind-content-database-source-field.db.test.ts`, `actions/content-database-source-actions.test.ts`, `actions/resync-content-database-source.db.test.ts` | `database-source-scope` | - |
30
+ | source-sync.database-source-bindings | source-sync | Attach, inspect, refresh, disconnect, join, and bind database sources | action-backed | `add-content-database-source-field-property`, `attach-content-database-source`, `bind-content-database-source-field`, `change-content-database-source-role`, `disconnect-content-database-source`, `get-content-database-source`, `list-builder-cms-models`, `list-notion-database-sources`, `preview-content-database-source-attach`, `refresh-content-database-source`, `suggest-source-join-key` | `app/components/editor/DocumentDatabase.tsx`, `app/components/editor/DocumentProperties.tsx`, `app/components/editor/database/DatabaseView.tsx`, `app/hooks/use-content-database.ts` | Mounted database source metadata, fields, source role, join keys, and source-field/property bindings are stored and refreshed. | - | - | P0 | covered | `actions/bind-content-database-source-field.db.test.ts`, `actions/content-database-source-actions.test.ts`, `actions/resync-content-database-source.db.test.ts` | `database-source-scope` | - |
30
31
  | source-sync.provider-api-and-staged-datasets | source-sync | Inspect provider APIs and stage/query/delete large provider datasets | action-backed | `delete-staged-dataset`, `list-staged-datasets`, `provider-api-catalog`, `provider-api-docs`, `provider-api-request`, `query-staged-dataset` | `actions/provider-api-catalog.ts`, `actions/provider-api-docs.ts`, `actions/provider-api-request.ts`, `actions/query-staged-dataset.ts` | Provider API metadata and staged dataset scratch storage support scoped agent/source analysis. | - | - | P1 | seeded | - | - | - |
31
32
  | versions.history-and-restore | versions | Open version history and restore a previous document version | action-backed | `list-document-versions`, `restore-document-version` | `app/components/editor/VersionHistoryPanel.tsx`, `app/hooks/use-document-versions.ts` | Document versions are listed and selected versions can restore the document while snapshotting current state. | - | - | P0 | seeded | - | - | - |
32
33
  | workspace.spaces-and-files-catalog | workspace | Provision, navigate, and delete Content spaces through Files and Workspaces with personal expansion state | action-backed | `backfill-content-files`, `create-content-space`, `delete-content-space`, `ensure-content-spaces`, `get-content-sidebar-state`, `list-content-spaces`, `update-content-sidebar-state` | `app/components/sidebar/DocumentSidebar.tsx`, `app/hooks/use-content-spaces.ts` | Personal and organization spaces, user-created workspaces, their canonical Files databases, the personal Workspaces catalog, and each user's sidebar expansion state are stored and reconciled in SQL; deleting a user-created workspace atomically removes its catalog row and contents. | - | - | P0 | covered | `actions/content-spaces.db.test.ts`, `actions/content-files.db.test.ts`, `actions/content-sidebar-state.test.ts` | - | - |
@@ -266,6 +266,30 @@ export const parityMatrix: ParityRow[] = [
266
266
  coverageRefs: ["actions/content-database-lifecycle.db.test.ts"],
267
267
  evalScenarioIds: ["database-source-scope"],
268
268
  },
269
+ {
270
+ id: "database.table-query-page",
271
+ surface: "database",
272
+ label: "Query one constrained page while retaining database metadata",
273
+ uiEntrypoints: [
274
+ "app/components/editor/database/DatabaseView.tsx",
275
+ "app/hooks/use-content-database.ts",
276
+ ],
277
+ durableEffect: null,
278
+ uiImplementation:
279
+ "The table view loads changed search, filter, and sort results through a page-only action while the base database response remains visible.",
280
+ status: "action-backed",
281
+ actions: ["query-content-database-items"],
282
+ exception:
283
+ "This UI-only bounded projection is intentionally hidden with agentTool: false; agents use get-content-database for the complete database contract.",
284
+ reliabilityRisk: "none",
285
+ spinePriority: "P0",
286
+ testCoverage: "covered",
287
+ followUpPR: null,
288
+ coverageRefs: [
289
+ "actions/content-database-lifecycle.db.test.ts",
290
+ "app/hooks/use-content-database.test.ts",
291
+ ],
292
+ },
269
293
  {
270
294
  id: "database.form-submissions",
271
295
  surface: "database",
@@ -402,6 +426,7 @@ export const parityMatrix: ParityRow[] = [
402
426
  "get-content-database-source",
403
427
  "list-builder-cms-models",
404
428
  "list-notion-database-sources",
429
+ "preview-content-database-source-attach",
405
430
  "refresh-content-database-source",
406
431
  "suggest-source-join-key",
407
432
  ],
@@ -276,6 +276,13 @@ export interface ContentDatabaseFilter {
276
276
  parentFilterGroupId?: string;
277
277
  }
278
278
 
279
+ export interface ContentDatabaseTableQuery {
280
+ search: string;
281
+ filters: ContentDatabaseFilter[];
282
+ sorts: ContentDatabaseSort[];
283
+ filterMode: ContentDatabaseFilterMode;
284
+ }
285
+
279
286
  export type ContentDatabaseColumnCalculation =
280
287
  | "count_all"
281
288
  | "count_values"
@@ -707,6 +714,10 @@ export interface ContentDatabaseSource {
707
714
  fields: ContentDatabaseSourceFieldMapping[];
708
715
  rows: ContentDatabaseSourceRow[];
709
716
  changeSets: ContentDatabaseSourceChangeSet[];
717
+ projection?: {
718
+ rows: "complete" | "page";
719
+ changeSets: "complete" | "page";
720
+ };
710
721
  bodyHydration?: ContentDatabaseBodyHydrationSummary;
711
722
  }
712
723
 
@@ -774,17 +785,32 @@ export interface ContentDatabaseResponse {
774
785
  hasMore: boolean;
775
786
  };
776
787
  createdItemId?: string;
788
+ createdItem?: ContentDatabaseItem;
777
789
  createdDocumentId?: string;
778
790
  createdDocumentUpdatedAt?: string;
779
791
  duplicatedItemId?: string;
780
792
  duplicatedDocumentId?: string;
781
793
  duplicatedItemIds?: string[];
794
+ duplicatedItems?: ContentDatabaseItem[];
782
795
  duplicatedDocumentIds?: string[];
783
796
  deletedItemIds?: string[];
784
797
  deletedDocumentIds?: string[];
785
798
  timings?: BuilderActionTiming[];
799
+ tableQueryMode?: "server" | "client-required";
800
+ /** Client-only optimistic state while real provider rows are being attached. */
801
+ attachPreview?: {
802
+ sourceTable: string;
803
+ fetchedAt: string;
804
+ importedItemCount?: number;
805
+ complete?: boolean;
806
+ };
786
807
  }
787
808
 
809
+ export type ContentDatabaseItemsPageResponse = Pick<
810
+ ContentDatabaseResponse,
811
+ "items" | "source" | "sources" | "pagination" | "tableQueryMode"
812
+ >;
813
+
788
814
  export interface BuilderActionTiming {
789
815
  name: string;
790
816
  durationMs: number;
@@ -897,6 +923,8 @@ export interface AttachContentDatabaseSourceRequest {
897
923
  sourceType?: ContentDatabaseSourceType;
898
924
  sourceName?: string;
899
925
  sourceTable?: string;
926
+ /** Projected Builder model fields already visible in the model picker. */
927
+ builderFieldPaths?: string[];
900
928
  /** "items" adds more rows; "details" joins fields onto existing rows. */
901
929
  relationshipMode?: "items" | "details";
902
930
  join?: ContentDatabaseSourceJoinRequest;
@@ -906,6 +934,31 @@ export interface AttachContentDatabaseSourceRequest {
906
934
  offset?: number;
907
935
  }
908
936
 
937
+ export interface ContentDatabaseSourceAttachmentAck {
938
+ responseProjection: "ack";
939
+ databaseId: string;
940
+ documentId: string;
941
+ sourceId: string;
942
+ sourceType: ContentDatabaseSourceType;
943
+ sourceTable: string;
944
+ importedItemCount: number;
945
+ fetchedAt: string;
946
+ }
947
+
948
+ export type ContentDatabaseSourceAttachmentResult =
949
+ | ContentDatabaseResponse
950
+ | ContentDatabaseSourceAttachmentAck;
951
+
952
+ export interface BuilderCmsAttachPreviewResponse {
953
+ databaseId: string;
954
+ documentId: string;
955
+ sourceTable: string;
956
+ base: ContentDatabaseResponse;
957
+ items: ContentDatabaseItem[];
958
+ fetchedAt: string;
959
+ hasMore: boolean;
960
+ }
961
+
909
962
  export interface ChangeContentDatabaseSourceRoleRequest {
910
963
  databaseId?: string;
911
964
  documentId?: string;
@@ -985,6 +1038,7 @@ export interface RefreshContentDatabaseSourceRequest {
985
1038
  documentId?: string;
986
1039
  sourceId?: string;
987
1040
  fullRefresh?: boolean;
1041
+ finishBuilderPagination?: boolean;
988
1042
  expectedBuilderContinuationOffset?: number;
989
1043
  }
990
1044
 
@@ -1259,10 +1313,6 @@ export interface PreviewBuilderSourceReviewResponse {
1259
1313
  }
1260
1314
 
1261
1315
  export interface PrepareBuilderSourceReviewResponse {
1262
- database: ContentDatabase;
1263
- properties: DocumentProperty[];
1264
- items: ContentDatabaseItem[];
1265
- source: ContentDatabaseSource | null;
1266
1316
  review: ContentDatabaseSourceReviewPayload;
1267
1317
  /**
1268
1318
  * Maps the operator-selected diff identities to the immutable change-set
@@ -0,0 +1,359 @@
1
+ import type {
2
+ ContentDatabaseFilter,
3
+ ContentDatabaseFilterMode,
4
+ ContentDatabaseItem,
5
+ ContentDatabaseSort,
6
+ ContentDatabaseTableQuery,
7
+ DocumentProperty,
8
+ } from "./api.js";
9
+ import { documentPropertyDatePart, formulaValueText } from "./properties.js";
10
+
11
+ export function applyContentDatabaseTableQuery(
12
+ items: ContentDatabaseItem[],
13
+ properties: DocumentProperty[],
14
+ query: ContentDatabaseTableQuery,
15
+ ) {
16
+ const search = query.search.trim().toLowerCase();
17
+ const searched = search
18
+ ? items.filter((item) =>
19
+ itemSearchText(item, properties).toLowerCase().includes(search),
20
+ )
21
+ : items;
22
+ const activeFilters = query.filters.filter(isActiveFilter);
23
+ const filtered = activeFilters.length
24
+ ? searched.filter((item) =>
25
+ itemMatchesFilterTree(
26
+ item,
27
+ properties,
28
+ activeFilters,
29
+ query.filterMode,
30
+ ),
31
+ )
32
+ : searched;
33
+
34
+ if (query.sorts.length === 0) return filtered;
35
+ return [...filtered].sort((left, right) => {
36
+ for (const sort of query.sorts) {
37
+ const comparison = compareSortValues(
38
+ itemSortValue(left, properties, sort),
39
+ itemSortValue(right, properties, sort),
40
+ );
41
+ if (comparison !== 0) {
42
+ return sort.direction === "asc" ? comparison : -comparison;
43
+ }
44
+ }
45
+ return 0;
46
+ });
47
+ }
48
+
49
+ export function contentDatabaseTableQueryUsesProperties(
50
+ query: ContentDatabaseTableQuery,
51
+ propertyIds: ReadonlySet<string>,
52
+ ) {
53
+ if (propertyIds.size === 0) return false;
54
+ if (query.search.trim()) return true;
55
+ return [...query.filters, ...query.sorts].some(
56
+ (constraint) =>
57
+ constraint.key !== "name" && propertyIds.has(constraint.key),
58
+ );
59
+ }
60
+
61
+ function itemMatchesFilterTree(
62
+ item: ContentDatabaseItem,
63
+ properties: DocumentProperty[],
64
+ filters: ContentDatabaseFilter[],
65
+ filterMode: ContentDatabaseFilterMode,
66
+ ) {
67
+ const rootFilters = filters.filter((filter) => !filter.parentFilterGroupId);
68
+ const nestedGroups = new Map<string, ContentDatabaseFilter[]>();
69
+ for (const filter of filters) {
70
+ if (!filter.parentFilterGroupId || !filter.filterGroupId) continue;
71
+ nestedGroups.set(filter.filterGroupId, [
72
+ ...(nestedGroups.get(filter.filterGroupId) ?? []),
73
+ filter,
74
+ ]);
75
+ }
76
+ const matches = [
77
+ ...rootFilters.map((filter) => itemMatchesFilter(item, properties, filter)),
78
+ ...[...nestedGroups.values()].map((group) =>
79
+ combineMatches(
80
+ group.map((filter) => itemMatchesFilter(item, properties, filter)),
81
+ filterMode,
82
+ ),
83
+ ),
84
+ ];
85
+ return combineMatches(matches, filterMode);
86
+ }
87
+
88
+ function combineMatches(
89
+ matches: boolean[],
90
+ filterMode: ContentDatabaseFilterMode,
91
+ ) {
92
+ if (matches.length === 0) return true;
93
+ return filterMode === "or" ? matches.some(Boolean) : matches.every(Boolean);
94
+ }
95
+
96
+ function isActiveFilter(filter: ContentDatabaseFilter) {
97
+ if (
98
+ !["is_empty", "is_not_empty", "is_checked", "is_unchecked"].includes(
99
+ filter.operator,
100
+ )
101
+ ) {
102
+ return selectedFilterValues(filter.value).length > 0;
103
+ }
104
+ return true;
105
+ }
106
+
107
+ function itemMatchesFilter(
108
+ item: ContentDatabaseItem,
109
+ properties: DocumentProperty[],
110
+ filter: ContentDatabaseFilter,
111
+ ) {
112
+ const value = itemFilterValue(item, properties, filter.key);
113
+ const property = itemFilterProperty(item, properties, filter.key);
114
+ if (filter.operator === "is_empty") return !value.trim();
115
+ if (filter.operator === "is_not_empty") return !!value.trim();
116
+ if (filter.operator === "is_checked") return property?.value === true;
117
+ if (filter.operator === "is_unchecked") return property?.value !== true;
118
+
119
+ if (filter.operator === "greater_than" || filter.operator === "less_than") {
120
+ const current = propertyNumberValue(property);
121
+ const target = Number(filter.value.trim());
122
+ if (!Number.isFinite(current) || !Number.isFinite(target)) return false;
123
+ return filter.operator === "greater_than"
124
+ ? current > target
125
+ : current < target;
126
+ }
127
+
128
+ if (
129
+ filter.operator === "before" ||
130
+ filter.operator === "after" ||
131
+ filter.operator === "between"
132
+ ) {
133
+ const current = propertyDateValue(property);
134
+ if (!Number.isFinite(current)) return false;
135
+ if (filter.operator === "between") {
136
+ const range = filterDateRange(filter.value);
137
+ return !!range && current >= range[0] && current <= range[1];
138
+ }
139
+ const target = new Date(filter.value.trim()).getTime();
140
+ if (!Number.isFinite(target)) return false;
141
+ return filter.operator === "before" ? current < target : current > target;
142
+ }
143
+
144
+ const candidateValues = itemFilterCandidateValues(
145
+ item,
146
+ properties,
147
+ filter.key,
148
+ ).map((candidate) => candidate.trim().toLowerCase());
149
+ const selectedValues = selectedFilterValues(filter.value).map((candidate) =>
150
+ candidate.trim().toLowerCase(),
151
+ );
152
+ const normalizedValue = value.trim().toLowerCase();
153
+ const normalizedFilter = selectedValues[0] ?? "";
154
+ const usesDiscreteValues =
155
+ property?.definition.type === "select" ||
156
+ property?.definition.type === "status" ||
157
+ property?.definition.type === "multi_select" ||
158
+ property?.definition.type === "person";
159
+ if (
160
+ usesDiscreteValues &&
161
+ (filter.operator === "equals" || filter.operator === "contains")
162
+ ) {
163
+ return selectedValues.some((filterValue) =>
164
+ candidateValues.includes(filterValue),
165
+ );
166
+ }
167
+ if (usesDiscreteValues && filter.operator === "does_not_equal") {
168
+ return selectedValues.every(
169
+ (filterValue) => !candidateValues.includes(filterValue),
170
+ );
171
+ }
172
+ if (filter.operator === "equals") {
173
+ return candidateValues.includes(normalizedFilter);
174
+ }
175
+ if (filter.operator === "does_not_equal") {
176
+ return !candidateValues.includes(normalizedFilter);
177
+ }
178
+ return normalizedValue.includes(normalizedFilter);
179
+ }
180
+
181
+ function selectedFilterValues(value: string) {
182
+ const trimmed = value.trim();
183
+ if (!trimmed) return [];
184
+ try {
185
+ const parsed = JSON.parse(trimmed) as unknown;
186
+ if (!Array.isArray(parsed)) return [trimmed];
187
+ return [
188
+ ...new Set(
189
+ parsed
190
+ .filter((item): item is string => typeof item === "string")
191
+ .map((item) => item.trim())
192
+ .filter(Boolean),
193
+ ),
194
+ ];
195
+ } catch {
196
+ return [trimmed];
197
+ }
198
+ }
199
+
200
+ function filterDateRange(value: string): [number, number] | null {
201
+ const values = selectedFilterValues(value);
202
+ const start = new Date(values[0] ?? "").getTime();
203
+ const end = new Date(values[1] ?? "").getTime();
204
+ if (!Number.isFinite(start) || !Number.isFinite(end)) return null;
205
+ return start <= end ? [start, end] : [end, start];
206
+ }
207
+
208
+ function itemSearchText(
209
+ item: ContentDatabaseItem,
210
+ properties: DocumentProperty[],
211
+ ) {
212
+ return [
213
+ item.document.title || "Untitled",
214
+ ...properties.map((property) =>
215
+ propertyValueText(
216
+ item.properties.find(
217
+ (candidate) => candidate.definition.id === property.definition.id,
218
+ ) ?? property,
219
+ ),
220
+ ),
221
+ ].join(" ");
222
+ }
223
+
224
+ function itemSortValue(
225
+ item: ContentDatabaseItem,
226
+ properties: DocumentProperty[],
227
+ sort: ContentDatabaseSort,
228
+ ) {
229
+ if (sort.key === "name") return item.document.title || "";
230
+ return propertyValueText(
231
+ item.properties.find((candidate) => candidate.definition.id === sort.key) ??
232
+ properties.find((candidate) => candidate.definition.id === sort.key) ??
233
+ null,
234
+ );
235
+ }
236
+
237
+ function itemFilterValue(
238
+ item: ContentDatabaseItem,
239
+ properties: DocumentProperty[],
240
+ key: string,
241
+ ) {
242
+ if (key === "name") return item.document.title || "";
243
+ return propertyValueText(itemFilterProperty(item, properties, key));
244
+ }
245
+
246
+ function itemFilterProperty(
247
+ item: ContentDatabaseItem,
248
+ properties: DocumentProperty[],
249
+ key: string,
250
+ ) {
251
+ if (key === "name") return null;
252
+ return (
253
+ item.properties.find((candidate) => candidate.definition.id === key) ??
254
+ properties.find((candidate) => candidate.definition.id === key) ??
255
+ null
256
+ );
257
+ }
258
+
259
+ function itemFilterCandidateValues(
260
+ item: ContentDatabaseItem,
261
+ properties: DocumentProperty[],
262
+ key: string,
263
+ ) {
264
+ if (key === "name") return [item.document.title || ""];
265
+ const property = itemFilterProperty(item, properties, key);
266
+ if (!property || property.value === null || property.value === undefined) {
267
+ return [""];
268
+ }
269
+ if (Array.isArray(property.value)) {
270
+ return property.value.flatMap((id) => {
271
+ const optionName =
272
+ property.definition.options.options?.find((option) => option.id === id)
273
+ ?.name ?? id;
274
+ return [id, optionName];
275
+ });
276
+ }
277
+ if (
278
+ property.definition.type === "select" ||
279
+ property.definition.type === "status"
280
+ ) {
281
+ const id = String(property.value);
282
+ const optionName =
283
+ property.definition.options.options?.find((option) => option.id === id)
284
+ ?.name ?? id;
285
+ return [id, optionName];
286
+ }
287
+ return [propertyValueText(property)];
288
+ }
289
+
290
+ function propertyValueText(property: DocumentProperty | null | undefined) {
291
+ if (!property || property.value === null || property.value === undefined) {
292
+ return "";
293
+ }
294
+ if (Array.isArray(property.value)) {
295
+ return property.value
296
+ .map(
297
+ (id) =>
298
+ property.definition.options.options?.find(
299
+ (option) => option.id === id,
300
+ )?.name ?? id,
301
+ )
302
+ .join(" ");
303
+ }
304
+ if (
305
+ property.definition.type === "select" ||
306
+ property.definition.type === "status"
307
+ ) {
308
+ return (
309
+ property.definition.options.options?.find(
310
+ (option) => option.id === String(property.value),
311
+ )?.name ?? String(property.value)
312
+ );
313
+ }
314
+ if (property.definition.type === "checkbox") {
315
+ return property.value ? "Checked" : "Unchecked";
316
+ }
317
+ return formulaValueText(property.value);
318
+ }
319
+
320
+ function compareSortValues(left: string, right: string) {
321
+ const leftNumber = Number(left);
322
+ const rightNumber = Number(right);
323
+ if (
324
+ left.trim() &&
325
+ right.trim() &&
326
+ Number.isFinite(leftNumber) &&
327
+ Number.isFinite(rightNumber)
328
+ ) {
329
+ return leftNumber - rightNumber;
330
+ }
331
+ return left.localeCompare(right, undefined, {
332
+ numeric: true,
333
+ sensitivity: "base",
334
+ });
335
+ }
336
+
337
+ function propertyNumberValue(property: DocumentProperty | null | undefined) {
338
+ if (
339
+ !property ||
340
+ property.value === null ||
341
+ property.value === undefined ||
342
+ property.value === ""
343
+ ) {
344
+ return Number.NaN;
345
+ }
346
+ const value =
347
+ typeof property.value === "number"
348
+ ? property.value
349
+ : Number(String(property.value).trim());
350
+ return Number.isFinite(value) ? value : Number.NaN;
351
+ }
352
+
353
+ function propertyDateValue(property: DocumentProperty | null | undefined) {
354
+ if (!property || !property.value) return Number.NaN;
355
+ const value = new Date(
356
+ documentPropertyDatePart(property.value, "start") || String(property.value),
357
+ ).getTime();
358
+ return Number.isFinite(value) ? value : Number.NaN;
359
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"use-action.d.ts","sourceRoot":"","sources":["../../src/client/use-action.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"use-action.d.ts","sourceRoot":"","sources":["../../src/client/use-action.ts"],"names":[],"mappings":"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"}
@@ -130,6 +130,12 @@ function appendActionQueryParam(qs, key, value) {
130
130
  }
131
131
  return;
132
132
  }
133
+ if (typeof value === "object") {
134
+ // defineAction restores JSON strings when the schema expects an object.
135
+ // Preserve nested GET params instead of collapsing them to "[object Object]".
136
+ qs.append(key, JSON.stringify(value));
137
+ return;
138
+ }
133
139
  qs.append(key, String(value));
134
140
  }
135
141
  /**