@agent-native/core 0.114.0 → 0.114.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/corpus/README.md +2 -2
- package/corpus/core/CHANGELOG.md +9 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/client/build-compatibility.ts +77 -0
- package/corpus/core/src/client/route-chunk-recovery/index.ts +1 -0
- package/corpus/core/src/client/use-action.ts +32 -1
- package/corpus/core/src/deploy/build.ts +4 -1
- package/corpus/core/src/org/context.ts +56 -30
- package/corpus/core/src/server/action-routes.ts +52 -2
- package/corpus/core/src/server/auth-plugin.ts +9 -4
- package/corpus/core/src/server/better-auth-instance.ts +5 -1
- package/corpus/core/src/shared/mcp-embed-headers.ts +1 -1
- package/corpus/core/src/vite/client.ts +17 -0
- package/corpus/templates/content/AGENTS.md +2 -0
- package/corpus/templates/content/actions/_content-database-personal-view.ts +49 -5
- package/corpus/templates/content/actions/_content-document-access.ts +21 -0
- package/corpus/templates/content/actions/_content-favorites.ts +91 -0
- package/corpus/templates/content/actions/_content-files.ts +79 -23
- package/corpus/templates/content/actions/_content-sidebar-state.ts +24 -0
- package/corpus/templates/content/actions/_content-space-access.ts +15 -17
- package/corpus/templates/content/actions/_content-space-catalog-guards.ts +20 -0
- package/corpus/templates/content/actions/_content-spaces.ts +364 -167
- package/corpus/templates/content/actions/_database-source-utils.ts +8 -0
- package/corpus/templates/content/actions/_database-utils.ts +249 -15
- package/corpus/templates/content/actions/_delete-content-space.ts +84 -0
- package/corpus/templates/content/actions/_files-system-properties.ts +499 -0
- package/corpus/templates/content/actions/_local-file-documents.ts +0 -1
- package/corpus/templates/content/actions/_property-utils.ts +18 -9
- package/corpus/templates/content/actions/bind-content-database-source-field.ts +3 -0
- package/corpus/templates/content/actions/configure-document-property.ts +3 -0
- package/corpus/templates/content/actions/create-content-database.ts +2 -2
- package/corpus/templates/content/actions/create-content-space.ts +34 -0
- package/corpus/templates/content/actions/create-document.ts +4 -1
- package/corpus/templates/content/actions/delete-content-space.ts +19 -0
- package/corpus/templates/content/actions/delete-database-items.ts +28 -1
- package/corpus/templates/content/actions/delete-document-property.ts +3 -0
- package/corpus/templates/content/actions/delete-document.ts +39 -2
- package/corpus/templates/content/actions/duplicate-database-item.ts +6 -0
- package/corpus/templates/content/actions/duplicate-database-items.ts +6 -0
- package/corpus/templates/content/actions/duplicate-document-property.ts +3 -0
- package/corpus/templates/content/actions/get-content-database.ts +11 -2
- package/corpus/templates/content/actions/get-content-sidebar-state.ts +23 -0
- package/corpus/templates/content/actions/get-document.ts +32 -7
- package/corpus/templates/content/actions/import-content-source.ts +33 -1
- package/corpus/templates/content/actions/list-content-spaces.ts +34 -2
- package/corpus/templates/content/actions/list-documents.ts +39 -7
- package/corpus/templates/content/actions/reorder-document-property.ts +9 -0
- package/corpus/templates/content/actions/set-document-property.ts +41 -23
- package/corpus/templates/content/actions/share-local-file-document.ts +15 -0
- package/corpus/templates/content/actions/submit-content-database-form.ts +7 -0
- package/corpus/templates/content/actions/sync-local-folder-source.ts +14 -0
- package/corpus/templates/content/actions/update-content-database-view.ts +1 -0
- package/corpus/templates/content/actions/update-content-sidebar-state.ts +27 -0
- package/corpus/templates/content/actions/update-document.ts +158 -66
- package/corpus/templates/content/app/components/EmptyState.tsx +4 -57
- package/corpus/templates/content/app/components/editor/CommentsSidebar.tsx +8 -1
- package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +519 -178
- package/corpus/templates/content/app/components/editor/DocumentInfoPanel.tsx +39 -0
- package/corpus/templates/content/app/components/editor/DocumentProperties.tsx +1 -1
- package/corpus/templates/content/app/components/editor/DocumentToolbar.tsx +266 -7
- package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +430 -152
- package/corpus/templates/content/app/components/editor/database/sidebar.tsx +467 -66
- package/corpus/templates/content/app/components/editor/database/view-config.ts +9 -3
- package/corpus/templates/content/app/components/sidebar/DocumentSidebar.tsx +720 -196
- package/corpus/templates/content/app/components/sidebar/DocumentTreeItem.tsx +173 -1
- package/corpus/templates/content/app/components/sidebar/document-sidebar-sections.ts +1 -1
- package/corpus/templates/content/app/components/sidebar/select-content-space.ts +91 -16
- package/corpus/templates/content/app/entry.client.tsx +5 -1
- package/corpus/templates/content/app/hooks/use-content-database.ts +84 -3
- package/corpus/templates/content/app/hooks/use-content-spaces.ts +60 -2
- package/corpus/templates/content/app/hooks/use-create-page.ts +30 -0
- package/corpus/templates/content/app/hooks/use-documents.ts +270 -19
- package/corpus/templates/content/app/i18n-data.ts +243 -20
- package/corpus/templates/content/app/lib/local-folder-picker-safety.ts +54 -0
- package/corpus/templates/content/app/lib/local-folder-picker-support.ts +18 -0
- package/corpus/templates/content/app/routes/_app.favorites.tsx +30 -0
- package/corpus/templates/content/app/routes/_app.local-files.tsx +18 -6
- package/corpus/templates/content/changelog/2026-07-17-workspace-files-now-stay-in-a-loading-state-until-automatic-.md +6 -0
- package/corpus/templates/content/changelog/2026-07-17-workspace-switching-now-opens-the-selected-files-database-pr.md +6 -0
- package/corpus/templates/content/changelog/2026-07-17-workspaces-now-expand-above-their-own-files-list-without-dup.md +6 -0
- package/corpus/templates/content/changelog/2026-07-18-create-named-workspaces-each-with-its-own-private-files-data.md +6 -0
- package/corpus/templates/content/changelog/2026-07-18-favorites-align-with-workspace-and-file-rows-in-the-sidebar.md +6 -0
- package/corpus/templates/content/changelog/2026-07-18-local-folder-selection-no-longer-invokes-the-unsafe-native-p.md +6 -0
- package/corpus/templates/content/changelog/2026-07-18-made-content-workspaces-independently-expandable-restored-or.md +6 -0
- package/corpus/templates/content/changelog/2026-07-18-page-details-now-live-in-a-shared-info-and-comments-rail-dat.md +6 -0
- package/corpus/templates/content/changelog/2026-07-18-workspace-database-views-now-control-sidebar-workspace-navig.md +6 -0
- package/corpus/templates/content/changelog/2026-07-18-workspace-navigation-now-aligns-favorites-with-workspace-pag.md +6 -0
- package/corpus/templates/content/changelog/2026-07-19-favorites-now-open-as-a-table-and-compact-breadcrumbs-make-w.md +6 -0
- package/corpus/templates/content/changelog/2026-07-19-files-databases-now-start-unfiltered-sidebar-rows-exactly-fo.md +6 -0
- package/corpus/templates/content/changelog/2026-07-19-sidebar-page-trees-now-open-only-when-expanded-remember-thei.md +6 -0
- package/corpus/templates/content/changelog/2026-07-19-workspace-file-databases-now-use-the-workspace-name-open-as-.md +6 -0
- package/corpus/templates/content/changelog/2026-07-19-workspace-navigation-now-stays-stable-on-hover-uses-folder-i.md +6 -0
- package/corpus/templates/content/changelog/2026-07-19-workspace-rows-now-use-folder-icons-and-deleting-a-user-crea.md +6 -0
- package/corpus/templates/content/changelog/2026-07-19-workspaces-can-be-created-without-leaving-the-workspaces-dat.md +6 -0
- package/corpus/templates/content/e2e/global-setup.ts +3 -2
- package/corpus/templates/content/parity/matrix.md +28 -28
- package/corpus/templates/content/parity/matrix.ts +8 -2
- package/corpus/templates/content/server/db/index.ts +1 -0
- package/corpus/templates/content/server/db/schema.ts +10 -0
- package/corpus/templates/content/server/lib/documents.ts +12 -7
- package/corpus/templates/content/server/plugins/db.ts +17 -0
- package/corpus/templates/content/shared/api.ts +9 -0
- package/corpus/templates/content/shared/database-form.ts +11 -2
- package/corpus/templates/content/vite.config.ts +1 -0
- package/dist/client/build-compatibility.d.ts +11 -0
- package/dist/client/build-compatibility.d.ts.map +1 -0
- package/dist/client/build-compatibility.js +56 -0
- package/dist/client/build-compatibility.js.map +1 -0
- package/dist/client/route-chunk-recovery/index.d.ts +1 -0
- package/dist/client/route-chunk-recovery/index.d.ts.map +1 -1
- package/dist/client/route-chunk-recovery/index.js +1 -0
- package/dist/client/route-chunk-recovery/index.js.map +1 -1
- package/dist/client/use-action.d.ts.map +1 -1
- package/dist/client/use-action.js +19 -1
- package/dist/client/use-action.js.map +1 -1
- package/dist/collab/routes.d.ts +1 -1
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/deploy/build.js +2 -1
- package/dist/deploy/build.js.map +1 -1
- package/dist/file-upload/actions/upload-image.d.ts +1 -1
- package/dist/notifications/routes.d.ts +1 -1
- package/dist/observability/routes.d.ts +5 -5
- package/dist/org/context.d.ts +4 -2
- package/dist/org/context.d.ts.map +1 -1
- package/dist/org/context.js +44 -22
- package/dist/org/context.js.map +1 -1
- package/dist/progress/routes.d.ts +1 -1
- package/dist/provider-api/actions/custom-provider-registration.d.ts +10 -10
- package/dist/provider-api/actions/provider-api.d.ts +12 -12
- package/dist/provider-api/corpus-jobs.d.ts +2 -2
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/secrets/routes.d.ts +9 -9
- package/dist/server/action-routes.d.ts.map +1 -1
- package/dist/server/action-routes.js +32 -2
- package/dist/server/action-routes.js.map +1 -1
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/auth-plugin.d.ts.map +1 -1
- package/dist/server/auth-plugin.js +9 -5
- package/dist/server/auth-plugin.js.map +1 -1
- package/dist/server/better-auth-instance.d.ts.map +1 -1
- package/dist/server/better-auth-instance.js +4 -1
- package/dist/server/better-auth-instance.js.map +1 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/dist/shared/mcp-embed-headers.d.ts +1 -1
- package/dist/shared/mcp-embed-headers.d.ts.map +1 -1
- package/dist/shared/mcp-embed-headers.js +1 -1
- package/dist/shared/mcp-embed-headers.js.map +1 -1
- package/dist/vite/client.d.ts +6 -0
- package/dist/vite/client.d.ts.map +1 -1
- package/dist/vite/client.js +8 -0
- package/dist/vite/client.js.map +1 -1
- package/package.json +1 -1
- package/src/client/build-compatibility.ts +77 -0
- package/src/client/route-chunk-recovery/index.ts +1 -0
- package/src/client/use-action.ts +32 -1
- package/src/deploy/build.ts +4 -1
- package/src/org/context.ts +56 -30
- package/src/server/action-routes.ts +52 -2
- package/src/server/auth-plugin.ts +9 -4
- package/src/server/better-auth-instance.ts +5 -1
- package/src/shared/mcp-embed-headers.ts +1 -1
- package/src/vite/client.ts +17 -0
|
@@ -0,0 +1,499 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
|
|
3
|
+
import { accessFilter } from "@agent-native/core/sharing";
|
|
4
|
+
import { and, eq, inArray, isNull, lt, or } from "drizzle-orm";
|
|
5
|
+
|
|
6
|
+
import { getDb, schema } from "../server/db/index.js";
|
|
7
|
+
import type {
|
|
8
|
+
ContentDatabaseViewConfig,
|
|
9
|
+
DocumentProperty,
|
|
10
|
+
DocumentPropertyDefinition,
|
|
11
|
+
DocumentPropertyOption,
|
|
12
|
+
DocumentPropertySystemRole,
|
|
13
|
+
DocumentPropertyValue,
|
|
14
|
+
} from "../shared/api.js";
|
|
15
|
+
import {
|
|
16
|
+
parsePropertyOptions,
|
|
17
|
+
serializePropertyOptions,
|
|
18
|
+
} from "../shared/properties.js";
|
|
19
|
+
import {
|
|
20
|
+
defaultDatabaseViewConfig,
|
|
21
|
+
parseDatabaseViewConfig,
|
|
22
|
+
serializeDatabaseViewConfig,
|
|
23
|
+
} from "./_property-utils.js";
|
|
24
|
+
|
|
25
|
+
type Db = Pick<ReturnType<typeof getDb>, "select" | "insert" | "update">;
|
|
26
|
+
type Database = typeof schema.contentDatabases.$inferSelect;
|
|
27
|
+
type Document = Pick<
|
|
28
|
+
typeof schema.documents.$inferSelect,
|
|
29
|
+
"id" | "spaceId" | "parentId" | "ownerEmail" | "orgId"
|
|
30
|
+
>;
|
|
31
|
+
|
|
32
|
+
const FILES_SYSTEM_PROPERTIES_VERSION = 2;
|
|
33
|
+
|
|
34
|
+
const SYSTEM_PROPERTY_SPECS = [
|
|
35
|
+
{
|
|
36
|
+
role: "files_parent" as const,
|
|
37
|
+
name: "Parent",
|
|
38
|
+
type: "select" as const,
|
|
39
|
+
description: "The authorized parent page or database for this file.",
|
|
40
|
+
position: -200,
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
role: "files_source" as const,
|
|
44
|
+
name: "Source",
|
|
45
|
+
type: "multi_select" as const,
|
|
46
|
+
description: "The source system that supplies this file.",
|
|
47
|
+
position: -100,
|
|
48
|
+
},
|
|
49
|
+
] as const;
|
|
50
|
+
|
|
51
|
+
function systemPropertyId(
|
|
52
|
+
databaseId: string,
|
|
53
|
+
role: DocumentPropertySystemRole,
|
|
54
|
+
) {
|
|
55
|
+
return `content_files_property_${createHash("sha256")
|
|
56
|
+
.update(`${databaseId}:${role}`)
|
|
57
|
+
.digest("hex")
|
|
58
|
+
.slice(0, 32)}`;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function legacyFilesKindPropertyId(databaseId: string) {
|
|
62
|
+
return systemPropertyId(databaseId, "files_kind");
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function filesParentPropertyId(databaseId: string) {
|
|
66
|
+
return systemPropertyId(databaseId, "files_parent");
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function legacyFilesRootFilter(databaseId: string) {
|
|
70
|
+
return {
|
|
71
|
+
key: filesParentPropertyId(databaseId),
|
|
72
|
+
label: "Parent",
|
|
73
|
+
operator: "is_empty" as const,
|
|
74
|
+
value: "",
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function defaultFilesDatabaseViewConfig(
|
|
79
|
+
_databaseId: string,
|
|
80
|
+
): ContentDatabaseViewConfig {
|
|
81
|
+
return defaultDatabaseViewConfig("table");
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function migrateFilesDatabaseViewConfig(
|
|
85
|
+
config: ContentDatabaseViewConfig,
|
|
86
|
+
databaseId: string,
|
|
87
|
+
options: { removeLegacyRootFilter?: boolean } = {},
|
|
88
|
+
): ContentDatabaseViewConfig {
|
|
89
|
+
const legacyKindPropertyId = legacyFilesKindPropertyId(databaseId);
|
|
90
|
+
const legacyRootFilter = legacyFilesRootFilter(databaseId);
|
|
91
|
+
const migrateFilters = (
|
|
92
|
+
filters: ContentDatabaseViewConfig["filters"],
|
|
93
|
+
): ContentDatabaseViewConfig["filters"] =>
|
|
94
|
+
filters.flatMap((filter) => {
|
|
95
|
+
if (
|
|
96
|
+
options.removeLegacyRootFilter &&
|
|
97
|
+
filter.key === legacyRootFilter.key &&
|
|
98
|
+
filter.operator === legacyRootFilter.operator &&
|
|
99
|
+
filter.value === legacyRootFilter.value
|
|
100
|
+
) {
|
|
101
|
+
return [];
|
|
102
|
+
}
|
|
103
|
+
if (filter.key !== legacyKindPropertyId) return [filter];
|
|
104
|
+
if (
|
|
105
|
+
filter.operator === "does_not_equal" &&
|
|
106
|
+
filter.value === "database_row"
|
|
107
|
+
) {
|
|
108
|
+
return [];
|
|
109
|
+
}
|
|
110
|
+
return [];
|
|
111
|
+
});
|
|
112
|
+
return {
|
|
113
|
+
...config,
|
|
114
|
+
filters: migrateFilters(config.filters),
|
|
115
|
+
views: config.views.map((view) => ({
|
|
116
|
+
...view,
|
|
117
|
+
filters: migrateFilters(view.filters),
|
|
118
|
+
})),
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function storedOptions(role: DocumentPropertySystemRole) {
|
|
123
|
+
if (role === "files_source") {
|
|
124
|
+
return {
|
|
125
|
+
options: [{ id: "local", name: "Content", color: "gray" as const }],
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
return { options: [] };
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export async function ensureFilesSystemPropertyDefinitions(args: {
|
|
132
|
+
database: Database;
|
|
133
|
+
db?: Db;
|
|
134
|
+
now?: string;
|
|
135
|
+
}) {
|
|
136
|
+
if (args.database.systemRole !== "files") return;
|
|
137
|
+
const db = args.db ?? getDb();
|
|
138
|
+
const now = args.now ?? new Date().toISOString();
|
|
139
|
+
const existingDefinitions = await db
|
|
140
|
+
.select()
|
|
141
|
+
.from(schema.documentPropertyDefinitions)
|
|
142
|
+
.where(eq(schema.documentPropertyDefinitions.databaseId, args.database.id));
|
|
143
|
+
const existingSourceSystemProperty = existingDefinitions.find(
|
|
144
|
+
(definition) => definition.systemRole === "files_source",
|
|
145
|
+
);
|
|
146
|
+
const sourceIds = (
|
|
147
|
+
await db
|
|
148
|
+
.select({ id: schema.contentDatabaseSources.id })
|
|
149
|
+
.from(schema.contentDatabaseSources)
|
|
150
|
+
.where(eq(schema.contentDatabaseSources.databaseId, args.database.id))
|
|
151
|
+
).map((source) => source.id);
|
|
152
|
+
const legacySourceOptionIds = [...sourceIds, "local"].sort();
|
|
153
|
+
const legacySourceProperty = existingDefinitions.find((definition) => {
|
|
154
|
+
if (
|
|
155
|
+
definition.systemRole ||
|
|
156
|
+
definition.name !== "Source" ||
|
|
157
|
+
(definition.type !== "select" && definition.type !== "multi_select")
|
|
158
|
+
) {
|
|
159
|
+
return false;
|
|
160
|
+
}
|
|
161
|
+
const optionIds = (
|
|
162
|
+
parsePropertyOptions(definition.optionsJson).options ?? []
|
|
163
|
+
)
|
|
164
|
+
.map((option) => option.id)
|
|
165
|
+
.sort();
|
|
166
|
+
return (
|
|
167
|
+
sourceIds.length >= 2 &&
|
|
168
|
+
optionIds.length === legacySourceOptionIds.length &&
|
|
169
|
+
optionIds.every((id, index) => id === legacySourceOptionIds[index])
|
|
170
|
+
);
|
|
171
|
+
});
|
|
172
|
+
const adoptedLegacySource =
|
|
173
|
+
!existingSourceSystemProperty && Boolean(legacySourceProperty);
|
|
174
|
+
if (adoptedLegacySource && legacySourceProperty) {
|
|
175
|
+
await db
|
|
176
|
+
.update(schema.documentPropertyDefinitions)
|
|
177
|
+
.set({
|
|
178
|
+
systemRole: "files_source",
|
|
179
|
+
type: "multi_select",
|
|
180
|
+
updatedAt: now,
|
|
181
|
+
})
|
|
182
|
+
.where(
|
|
183
|
+
eq(schema.documentPropertyDefinitions.id, legacySourceProperty.id),
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
const existingRoles = new Set(
|
|
187
|
+
existingDefinitions
|
|
188
|
+
.map((definition) => definition.systemRole)
|
|
189
|
+
.filter((role): role is string => Boolean(role)),
|
|
190
|
+
);
|
|
191
|
+
if (legacySourceProperty && !existingSourceSystemProperty) {
|
|
192
|
+
existingRoles.add("files_source");
|
|
193
|
+
}
|
|
194
|
+
const missingDefinitions = SYSTEM_PROPERTY_SPECS.filter(
|
|
195
|
+
(spec) => !existingRoles.has(spec.role),
|
|
196
|
+
).map((spec) => ({
|
|
197
|
+
id: systemPropertyId(args.database.id, spec.role),
|
|
198
|
+
ownerEmail: args.database.ownerEmail,
|
|
199
|
+
orgId: args.database.orgId,
|
|
200
|
+
databaseId: args.database.id,
|
|
201
|
+
systemRole: spec.role,
|
|
202
|
+
name: spec.name,
|
|
203
|
+
type: spec.type,
|
|
204
|
+
description: spec.description,
|
|
205
|
+
visibility: "always_show",
|
|
206
|
+
optionsJson: serializePropertyOptions(storedOptions(spec.role)),
|
|
207
|
+
position: spec.position,
|
|
208
|
+
createdAt: now,
|
|
209
|
+
updatedAt: now,
|
|
210
|
+
}));
|
|
211
|
+
if (missingDefinitions.length > 0) {
|
|
212
|
+
await db
|
|
213
|
+
.insert(schema.documentPropertyDefinitions)
|
|
214
|
+
.values(missingDefinitions)
|
|
215
|
+
.onConflictDoNothing();
|
|
216
|
+
}
|
|
217
|
+
const parsedStored = parseDatabaseViewConfig(args.database.viewConfigJson);
|
|
218
|
+
const normalizedStored = serializeDatabaseViewConfig(parsedStored);
|
|
219
|
+
const migratedStored = serializeDatabaseViewConfig(
|
|
220
|
+
migrateFilesDatabaseViewConfig(parsedStored, args.database.id, {
|
|
221
|
+
removeLegacyRootFilter:
|
|
222
|
+
args.database.filesSystemPropertiesSeeded <
|
|
223
|
+
FILES_SYSTEM_PROPERTIES_VERSION,
|
|
224
|
+
}),
|
|
225
|
+
);
|
|
226
|
+
const untouchedLegacyDefaults = new Set([
|
|
227
|
+
serializeDatabaseViewConfig(defaultDatabaseViewConfig("sidebar")),
|
|
228
|
+
serializeDatabaseViewConfig(defaultDatabaseViewConfig("table")),
|
|
229
|
+
]);
|
|
230
|
+
const viewConfigJson = untouchedLegacyDefaults.has(normalizedStored)
|
|
231
|
+
? serializeDatabaseViewConfig(
|
|
232
|
+
defaultFilesDatabaseViewConfig(args.database.id),
|
|
233
|
+
)
|
|
234
|
+
: migratedStored !== normalizedStored
|
|
235
|
+
? migratedStored
|
|
236
|
+
: args.database.viewConfigJson;
|
|
237
|
+
if (
|
|
238
|
+
missingDefinitions.length === 0 &&
|
|
239
|
+
!adoptedLegacySource &&
|
|
240
|
+
viewConfigJson === args.database.viewConfigJson &&
|
|
241
|
+
args.database.filesSystemPropertiesSeeded ===
|
|
242
|
+
FILES_SYSTEM_PROPERTIES_VERSION
|
|
243
|
+
) {
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
await db
|
|
247
|
+
.update(schema.contentDatabases)
|
|
248
|
+
.set({
|
|
249
|
+
viewConfigJson,
|
|
250
|
+
filesSystemPropertiesSeeded: FILES_SYSTEM_PROPERTIES_VERSION,
|
|
251
|
+
updatedAt: now,
|
|
252
|
+
})
|
|
253
|
+
.where(eq(schema.contentDatabases.id, args.database.id));
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
export async function repairFilesSystemPropertyDefinitions() {
|
|
257
|
+
const db = getDb();
|
|
258
|
+
const databases = await db
|
|
259
|
+
.select()
|
|
260
|
+
.from(schema.contentDatabases)
|
|
261
|
+
.where(
|
|
262
|
+
and(
|
|
263
|
+
eq(schema.contentDatabases.systemRole, "files"),
|
|
264
|
+
lt(
|
|
265
|
+
schema.contentDatabases.filesSystemPropertiesSeeded,
|
|
266
|
+
FILES_SYSTEM_PROPERTIES_VERSION,
|
|
267
|
+
),
|
|
268
|
+
isNull(schema.contentDatabases.deletedAt),
|
|
269
|
+
),
|
|
270
|
+
);
|
|
271
|
+
for (const database of databases) {
|
|
272
|
+
await db.transaction((tx) =>
|
|
273
|
+
ensureFilesSystemPropertyDefinitions({ database, db: tx }),
|
|
274
|
+
);
|
|
275
|
+
}
|
|
276
|
+
return databases.length;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function safeOption(
|
|
280
|
+
id: string,
|
|
281
|
+
name: string,
|
|
282
|
+
color: DocumentPropertyOption["color"] = "gray",
|
|
283
|
+
): DocumentPropertyOption {
|
|
284
|
+
return { id, name: name.trim() || "Untitled", color };
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function filesVisibilityFilter(database: Database) {
|
|
288
|
+
return database.orgId
|
|
289
|
+
? and(
|
|
290
|
+
eq(schema.documents.spaceId, database.spaceId!),
|
|
291
|
+
eq(schema.documents.orgId, database.orgId),
|
|
292
|
+
or(
|
|
293
|
+
eq(schema.documents.visibility, "org"),
|
|
294
|
+
eq(schema.documents.visibility, "public"),
|
|
295
|
+
),
|
|
296
|
+
or(
|
|
297
|
+
eq(schema.documents.hideFromSearch, 0),
|
|
298
|
+
isNull(schema.documents.hideFromSearch),
|
|
299
|
+
),
|
|
300
|
+
)
|
|
301
|
+
: and(
|
|
302
|
+
eq(schema.documents.spaceId, database.spaceId!),
|
|
303
|
+
eq(schema.documents.ownerEmail, database.ownerEmail),
|
|
304
|
+
);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
async function readableDatabaseIds(
|
|
308
|
+
db: Db,
|
|
309
|
+
databaseIds: string[],
|
|
310
|
+
options: { includeSystem?: boolean } = {},
|
|
311
|
+
) {
|
|
312
|
+
if (databaseIds.length === 0) return new Set<string>();
|
|
313
|
+
const databases = await db
|
|
314
|
+
.select({
|
|
315
|
+
id: schema.contentDatabases.id,
|
|
316
|
+
documentId: schema.contentDatabases.documentId,
|
|
317
|
+
})
|
|
318
|
+
.from(schema.contentDatabases)
|
|
319
|
+
.where(
|
|
320
|
+
and(
|
|
321
|
+
inArray(schema.contentDatabases.id, databaseIds),
|
|
322
|
+
isNull(schema.contentDatabases.deletedAt),
|
|
323
|
+
options.includeSystem === false
|
|
324
|
+
? isNull(schema.contentDatabases.systemRole)
|
|
325
|
+
: undefined,
|
|
326
|
+
),
|
|
327
|
+
);
|
|
328
|
+
if (databases.length === 0) return new Set<string>();
|
|
329
|
+
const readableDocuments = await db
|
|
330
|
+
.select({ id: schema.documents.id })
|
|
331
|
+
.from(schema.documents)
|
|
332
|
+
.where(
|
|
333
|
+
and(
|
|
334
|
+
inArray(
|
|
335
|
+
schema.documents.id,
|
|
336
|
+
databases.map((database) => database.documentId),
|
|
337
|
+
),
|
|
338
|
+
accessFilter(schema.documents, schema.documentShares),
|
|
339
|
+
),
|
|
340
|
+
);
|
|
341
|
+
const readableDocumentIds = new Set(readableDocuments.map((row) => row.id));
|
|
342
|
+
return new Set(
|
|
343
|
+
databases
|
|
344
|
+
.filter((database) => readableDocumentIds.has(database.documentId))
|
|
345
|
+
.map((database) => database.id),
|
|
346
|
+
);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
export async function filesSystemPropertyProjection(args: {
|
|
350
|
+
database: Database;
|
|
351
|
+
documents: Document[];
|
|
352
|
+
properties: DocumentProperty[];
|
|
353
|
+
}) {
|
|
354
|
+
const systemProperties = args.properties.filter((property) =>
|
|
355
|
+
SYSTEM_PROPERTY_SPECS.some(
|
|
356
|
+
(spec) => spec.role === property.definition.systemRole,
|
|
357
|
+
),
|
|
358
|
+
);
|
|
359
|
+
if (args.database.systemRole !== "files" || systemProperties.length === 0) {
|
|
360
|
+
return null;
|
|
361
|
+
}
|
|
362
|
+
const db = getDb();
|
|
363
|
+
const documentIds = args.documents.map((document) => document.id);
|
|
364
|
+
const parentIds = [
|
|
365
|
+
...new Set(
|
|
366
|
+
args.documents
|
|
367
|
+
.map((document) => document.parentId)
|
|
368
|
+
.filter((id): id is string => Boolean(id)),
|
|
369
|
+
),
|
|
370
|
+
];
|
|
371
|
+
const parents = parentIds.length
|
|
372
|
+
? await db
|
|
373
|
+
.select({ id: schema.documents.id, title: schema.documents.title })
|
|
374
|
+
.from(schema.documents)
|
|
375
|
+
.where(
|
|
376
|
+
and(
|
|
377
|
+
inArray(schema.documents.id, parentIds),
|
|
378
|
+
filesVisibilityFilter(args.database),
|
|
379
|
+
accessFilter(schema.documents, schema.documentShares),
|
|
380
|
+
),
|
|
381
|
+
)
|
|
382
|
+
: [];
|
|
383
|
+
const parentById = new Map(parents.map((parent) => [parent.id, parent]));
|
|
384
|
+
|
|
385
|
+
const sourceRows = documentIds.length
|
|
386
|
+
? await db
|
|
387
|
+
.select({
|
|
388
|
+
documentId: schema.contentDatabaseSourceRows.documentId,
|
|
389
|
+
sourceId: schema.contentDatabaseSourceRows.sourceId,
|
|
390
|
+
})
|
|
391
|
+
.from(schema.contentDatabaseSourceRows)
|
|
392
|
+
.where(
|
|
393
|
+
inArray(schema.contentDatabaseSourceRows.documentId, documentIds),
|
|
394
|
+
)
|
|
395
|
+
: [];
|
|
396
|
+
const sourceIds = [...new Set(sourceRows.map((row) => row.sourceId))];
|
|
397
|
+
const sources = sourceIds.length
|
|
398
|
+
? await db
|
|
399
|
+
.select({
|
|
400
|
+
id: schema.contentDatabaseSources.id,
|
|
401
|
+
databaseId: schema.contentDatabaseSources.databaseId,
|
|
402
|
+
sourceName: schema.contentDatabaseSources.sourceName,
|
|
403
|
+
})
|
|
404
|
+
.from(schema.contentDatabaseSources)
|
|
405
|
+
.where(inArray(schema.contentDatabaseSources.id, sourceIds))
|
|
406
|
+
: [];
|
|
407
|
+
const readableSourceDatabaseIds = await readableDatabaseIds(
|
|
408
|
+
db,
|
|
409
|
+
sources
|
|
410
|
+
.map((source) => source.databaseId)
|
|
411
|
+
.filter((databaseId) => databaseId !== args.database.id),
|
|
412
|
+
);
|
|
413
|
+
readableSourceDatabaseIds.add(args.database.id);
|
|
414
|
+
const readableSourceById = new Map(
|
|
415
|
+
sources
|
|
416
|
+
.filter((source) => readableSourceDatabaseIds.has(source.databaseId))
|
|
417
|
+
.map((source) => [source.id, source]),
|
|
418
|
+
);
|
|
419
|
+
const sourceIdsByDocument = new Map<string, string[]>();
|
|
420
|
+
for (const row of sourceRows) {
|
|
421
|
+
if (!readableSourceById.has(row.sourceId)) continue;
|
|
422
|
+
sourceIdsByDocument.set(row.documentId, [
|
|
423
|
+
...(sourceIdsByDocument.get(row.documentId) ?? []),
|
|
424
|
+
row.sourceId,
|
|
425
|
+
]);
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
const parentOptions = parents.map((parent) =>
|
|
429
|
+
safeOption(parent.id, parent.title, "blue"),
|
|
430
|
+
);
|
|
431
|
+
const sourceOptions = [
|
|
432
|
+
safeOption("local", "Content"),
|
|
433
|
+
...[...readableSourceById.values()].map((source) =>
|
|
434
|
+
safeOption(source.id, source.sourceName, "green"),
|
|
435
|
+
),
|
|
436
|
+
];
|
|
437
|
+
const definitionByRole = new Map<
|
|
438
|
+
DocumentPropertySystemRole,
|
|
439
|
+
DocumentPropertyDefinition
|
|
440
|
+
>();
|
|
441
|
+
for (const property of systemProperties) {
|
|
442
|
+
const role = property.definition.systemRole!;
|
|
443
|
+
definitionByRole.set(role, {
|
|
444
|
+
...property.definition,
|
|
445
|
+
options: {
|
|
446
|
+
options: role === "files_parent" ? parentOptions : sourceOptions,
|
|
447
|
+
},
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
const valuesByDocumentId = new Map<
|
|
452
|
+
string,
|
|
453
|
+
Map<DocumentPropertySystemRole, DocumentPropertyValue>
|
|
454
|
+
>();
|
|
455
|
+
for (const document of args.documents) {
|
|
456
|
+
const sourceValues = [
|
|
457
|
+
...new Set(sourceIdsByDocument.get(document.id) ?? []),
|
|
458
|
+
];
|
|
459
|
+
valuesByDocumentId.set(
|
|
460
|
+
document.id,
|
|
461
|
+
new Map<DocumentPropertySystemRole, DocumentPropertyValue>([
|
|
462
|
+
[
|
|
463
|
+
"files_parent",
|
|
464
|
+
document.parentId && parentById.has(document.parentId)
|
|
465
|
+
? document.parentId
|
|
466
|
+
: null,
|
|
467
|
+
],
|
|
468
|
+
["files_source", sourceValues.length > 0 ? sourceValues : ["local"]],
|
|
469
|
+
]),
|
|
470
|
+
);
|
|
471
|
+
}
|
|
472
|
+
return { definitionByRole, valuesByDocumentId };
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
export function applyFilesSystemPropertyProjection(args: {
|
|
476
|
+
properties: DocumentProperty[];
|
|
477
|
+
projection: NonNullable<
|
|
478
|
+
Awaited<ReturnType<typeof filesSystemPropertyProjection>>
|
|
479
|
+
>;
|
|
480
|
+
documentId?: string;
|
|
481
|
+
}) {
|
|
482
|
+
return args.properties
|
|
483
|
+
.filter((property) => property.definition.systemRole !== "files_kind")
|
|
484
|
+
.map((property) => {
|
|
485
|
+
const role = property.definition.systemRole;
|
|
486
|
+
if (!role) return property;
|
|
487
|
+
return {
|
|
488
|
+
...property,
|
|
489
|
+
definition:
|
|
490
|
+
args.projection.definitionByRole.get(role) ?? property.definition,
|
|
491
|
+
value: args.documentId
|
|
492
|
+
? (args.projection.valuesByDocumentId
|
|
493
|
+
.get(args.documentId)
|
|
494
|
+
?.get(role) ?? null)
|
|
495
|
+
: null,
|
|
496
|
+
editable: false,
|
|
497
|
+
};
|
|
498
|
+
});
|
|
499
|
+
}
|
|
@@ -159,6 +159,7 @@ export function serializeDatabase(
|
|
|
159
159
|
id: database.id,
|
|
160
160
|
documentId: database.documentId,
|
|
161
161
|
title: database.title,
|
|
162
|
+
systemRole: database.systemRole,
|
|
162
163
|
description,
|
|
163
164
|
viewConfig: parseDatabaseViewConfig(database.viewConfigJson),
|
|
164
165
|
createdAt: database.createdAt,
|
|
@@ -260,10 +261,8 @@ function defaultDatabaseView(
|
|
|
260
261
|
? "Timeline"
|
|
261
262
|
: type === "form"
|
|
262
263
|
? "Form"
|
|
263
|
-
:
|
|
264
|
-
|
|
265
|
-
: "Table",
|
|
266
|
-
type,
|
|
264
|
+
: "Table",
|
|
265
|
+
type: type === "sidebar" ? "table" : type,
|
|
267
266
|
sorts: values.sorts ?? [],
|
|
268
267
|
filters: values.filters ?? [],
|
|
269
268
|
filterMode: normalizeDatabaseFilterMode(values.filterMode),
|
|
@@ -287,21 +286,23 @@ function normalizeDatabaseView(value: unknown): ContentDatabaseView | null {
|
|
|
287
286
|
if (!value || typeof value !== "object") return null;
|
|
288
287
|
const view = value as Partial<ContentDatabaseView>;
|
|
289
288
|
if (typeof view.id !== "string" || !view.id.trim()) return null;
|
|
289
|
+
const retiredSidebar = view.type === "sidebar";
|
|
290
290
|
const type =
|
|
291
291
|
view.type === "board" ||
|
|
292
292
|
view.type === "list" ||
|
|
293
293
|
view.type === "gallery" ||
|
|
294
294
|
view.type === "calendar" ||
|
|
295
295
|
view.type === "timeline" ||
|
|
296
|
-
view.type === "form"
|
|
297
|
-
view.type === "sidebar"
|
|
296
|
+
view.type === "form"
|
|
298
297
|
? view.type
|
|
299
298
|
: "table";
|
|
300
299
|
return {
|
|
301
300
|
id: view.id,
|
|
302
301
|
name:
|
|
303
302
|
typeof view.name === "string" && view.name.trim()
|
|
304
|
-
? view.name.trim()
|
|
303
|
+
? retiredSidebar && view.name.trim() === "Sidebar"
|
|
304
|
+
? "Table"
|
|
305
|
+
: view.name.trim()
|
|
305
306
|
: defaultDatabaseView({}, type).name,
|
|
306
307
|
type,
|
|
307
308
|
sorts: Array.isArray(view.sorts) ? view.sorts.filter(isDatabaseSort) : [],
|
|
@@ -507,6 +508,9 @@ export async function listPropertiesForDatabase(
|
|
|
507
508
|
definition: {
|
|
508
509
|
id: definition.id,
|
|
509
510
|
databaseId: definition.databaseId,
|
|
511
|
+
systemRole: definition.systemRole as
|
|
512
|
+
| import("../shared/api.js").DocumentPropertySystemRole
|
|
513
|
+
| null,
|
|
510
514
|
name: definition.name,
|
|
511
515
|
type,
|
|
512
516
|
description: definition.description,
|
|
@@ -530,7 +534,7 @@ export async function listPropertiesForDatabase(
|
|
|
530
534
|
blockFieldContent: blockContentByPropertyId.get(definition.id),
|
|
531
535
|
})
|
|
532
536
|
: parsePropertyValue(storedValue?.valueJson),
|
|
533
|
-
editable: !isComputedPropertyType(type),
|
|
537
|
+
editable: !definition.systemRole && !isComputedPropertyType(type),
|
|
534
538
|
};
|
|
535
539
|
});
|
|
536
540
|
|
|
@@ -576,6 +580,9 @@ function serializePropertyDefinition(
|
|
|
576
580
|
return {
|
|
577
581
|
id: definition.id,
|
|
578
582
|
databaseId: definition.databaseId,
|
|
583
|
+
systemRole: definition.systemRole as
|
|
584
|
+
| import("../shared/api.js").DocumentPropertySystemRole
|
|
585
|
+
| null,
|
|
579
586
|
name: definition.name,
|
|
580
587
|
type,
|
|
581
588
|
description: definition.description,
|
|
@@ -700,7 +707,9 @@ export async function listPropertiesForDatabaseDocuments(
|
|
|
700
707
|
),
|
|
701
708
|
})
|
|
702
709
|
: parsePropertyValue(storedValue?.valueJson),
|
|
703
|
-
editable:
|
|
710
|
+
editable:
|
|
711
|
+
!definition.systemRole &&
|
|
712
|
+
!isComputedPropertyType(propertyDefinition.type),
|
|
704
713
|
};
|
|
705
714
|
});
|
|
706
715
|
|
|
@@ -121,6 +121,9 @@ export default defineAction({
|
|
|
121
121
|
if (!property) {
|
|
122
122
|
throw new Error("Target column does not belong to this database.");
|
|
123
123
|
}
|
|
124
|
+
if (property.systemRole) {
|
|
125
|
+
throw new Error("System properties cannot be bound to source fields.");
|
|
126
|
+
}
|
|
124
127
|
// The auto-created "Source" tag is internal row-tagging, never a writable
|
|
125
128
|
// bind target.
|
|
126
129
|
if (
|
|
@@ -118,6 +118,9 @@ export default defineAction({
|
|
|
118
118
|
),
|
|
119
119
|
);
|
|
120
120
|
if (!existing) throw new Error(`Property "${args.id}" not found`);
|
|
121
|
+
if (existing.systemRole) {
|
|
122
|
+
throw new Error("System properties cannot be changed.");
|
|
123
|
+
}
|
|
121
124
|
if (
|
|
122
125
|
isComputedPropertyType(existing.type as DocumentPropertyType) &&
|
|
123
126
|
existing.type !== type
|
|
@@ -160,7 +160,7 @@ export async function resolveContentDatabaseSpace(
|
|
|
160
160
|
if (!userEmail) throw new Error("no authenticated user");
|
|
161
161
|
const provisioned = await provisionContentSpaces(db, userEmail);
|
|
162
162
|
const spaceId = args.spaceId ?? provisioned.personalSpaceId;
|
|
163
|
-
await resolveContentSpaceAccess(spaceId, "
|
|
163
|
+
await resolveContentSpaceAccess(spaceId, "contributor");
|
|
164
164
|
return spaceId;
|
|
165
165
|
}
|
|
166
166
|
|
|
@@ -288,7 +288,7 @@ export async function createContentDatabaseRecord(
|
|
|
288
288
|
}
|
|
289
289
|
const spaceAccess = await (
|
|
290
290
|
options.resolveSpaceAccess ?? resolveContentSpaceAccess
|
|
291
|
-
)(spaceId, "
|
|
291
|
+
)(spaceId, "contributor", { db });
|
|
292
292
|
if (spaceAccess.space.id !== spaceId) {
|
|
293
293
|
throw new Error("Resolved Content space does not match the request");
|
|
294
294
|
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { defineAction } from "@agent-native/core";
|
|
2
|
+
import { getRequestUserEmail } from "@agent-native/core/server/request-context";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
|
|
5
|
+
import { getDb } from "../server/db/index.js";
|
|
6
|
+
import {
|
|
7
|
+
provisionUserContentSpace,
|
|
8
|
+
systemIdsForContentSpace,
|
|
9
|
+
} from "./_content-spaces.js";
|
|
10
|
+
|
|
11
|
+
export default defineAction({
|
|
12
|
+
description:
|
|
13
|
+
"Create a private Content workspace with its own canonical Files database.",
|
|
14
|
+
schema: z.object({
|
|
15
|
+
name: z.string().trim().min(1).max(200),
|
|
16
|
+
requestId: z.string().trim().min(1).max(200),
|
|
17
|
+
propertyValues: z.record(z.string(), z.unknown()).optional(),
|
|
18
|
+
}),
|
|
19
|
+
run: async ({ name, requestId, propertyValues }) => {
|
|
20
|
+
const userEmail = getRequestUserEmail();
|
|
21
|
+
if (!userEmail) throw new Error("no authenticated user");
|
|
22
|
+
const result = await provisionUserContentSpace(getDb(), userEmail, {
|
|
23
|
+
workspaceId: requestId,
|
|
24
|
+
name,
|
|
25
|
+
propertyValues,
|
|
26
|
+
});
|
|
27
|
+
return {
|
|
28
|
+
...result,
|
|
29
|
+
filesDocumentId: systemIdsForContentSpace(result.spaceId, "files")
|
|
30
|
+
.documentId,
|
|
31
|
+
kind: "user" as const,
|
|
32
|
+
};
|
|
33
|
+
},
|
|
34
|
+
});
|
|
@@ -211,7 +211,10 @@ export default defineAction({
|
|
|
211
211
|
} else {
|
|
212
212
|
const provisioned = await provisionContentSpaces(db, currentUserEmail);
|
|
213
213
|
spaceId = args.spaceId ?? provisioned.personalSpaceId;
|
|
214
|
-
const spaceAccess = await resolveContentSpaceAccess(
|
|
214
|
+
const spaceAccess = await resolveContentSpaceAccess(
|
|
215
|
+
spaceId,
|
|
216
|
+
"contributor",
|
|
217
|
+
);
|
|
215
218
|
ownerEmail = currentUserEmail;
|
|
216
219
|
orgId = spaceAccess.space.orgId;
|
|
217
220
|
visibility = orgId ? "org" : "private";
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { defineAction } from "@agent-native/core";
|
|
2
|
+
import { writeAppState } from "@agent-native/core/application-state";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
|
|
5
|
+
import { getDb } from "../server/db/index.js";
|
|
6
|
+
import { deleteUserContentSpace } from "./_delete-content-space.js";
|
|
7
|
+
|
|
8
|
+
export default defineAction({
|
|
9
|
+
description:
|
|
10
|
+
"Permanently delete a user-created Content workspace and all content inside it. Personal and organization workspaces cannot be deleted.",
|
|
11
|
+
schema: z.object({
|
|
12
|
+
spaceId: z.string().trim().min(1),
|
|
13
|
+
}),
|
|
14
|
+
run: async ({ spaceId }) => {
|
|
15
|
+
const result = await deleteUserContentSpace(getDb(), spaceId);
|
|
16
|
+
await writeAppState("refresh-signal", { ts: Date.now() });
|
|
17
|
+
return { success: true, ...result };
|
|
18
|
+
},
|
|
19
|
+
});
|