@agent-native/core 0.114.0 → 0.114.2

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 (223) hide show
  1. package/corpus/README.md +2 -2
  2. package/corpus/core/CHANGELOG.md +15 -0
  3. package/corpus/core/package.json +1 -1
  4. package/corpus/core/src/cli/create.ts +23 -0
  5. package/corpus/core/src/cli/skills.ts +24 -0
  6. package/corpus/core/src/client/build-compatibility.ts +77 -0
  7. package/corpus/core/src/client/route-chunk-recovery/index.ts +1 -0
  8. package/corpus/core/src/client/use-action.ts +32 -1
  9. package/corpus/core/src/deploy/build.ts +4 -1
  10. package/corpus/core/src/org/context.ts +56 -30
  11. package/corpus/core/src/server/action-routes.ts +52 -2
  12. package/corpus/core/src/server/auth-plugin.ts +9 -4
  13. package/corpus/core/src/server/better-auth-instance.ts +5 -1
  14. package/corpus/core/src/shared/mcp-embed-headers.ts +1 -1
  15. package/corpus/core/src/templates/default/.agents/skills/agent-native-toolkit/SKILL.md +133 -0
  16. package/corpus/core/src/templates/default/AGENTS.md +4 -1
  17. package/corpus/core/src/templates/headless/.agents/skills/agent-native-toolkit/SKILL.md +133 -0
  18. package/corpus/core/src/templates/headless/AGENTS.md +5 -1
  19. package/corpus/core/src/templates/workspace-core/.agents/skills/agent-native-toolkit/SKILL.md +26 -0
  20. package/corpus/core/src/templates/workspace-core/AGENTS.md +3 -1
  21. package/corpus/core/src/templates/workspace-root/AGENTS.md +3 -1
  22. package/corpus/core/src/vite/client.ts +17 -0
  23. package/corpus/templates/analytics/.agents/skills/agent-native-toolkit/SKILL.md +133 -0
  24. package/corpus/templates/analytics/AGENTS.md +5 -0
  25. package/corpus/templates/assets/.agents/skills/agent-native-toolkit/SKILL.md +133 -0
  26. package/corpus/templates/assets/AGENTS.md +5 -0
  27. package/corpus/templates/brain/.agents/skills/agent-native-toolkit/SKILL.md +133 -0
  28. package/corpus/templates/brain/AGENTS.md +5 -0
  29. package/corpus/templates/calendar/.agents/skills/agent-native-toolkit/SKILL.md +133 -0
  30. package/corpus/templates/calendar/AGENTS.md +5 -0
  31. package/corpus/templates/chat/.agents/skills/agent-native-toolkit/SKILL.md +133 -0
  32. package/corpus/templates/chat/AGENTS.md +4 -1
  33. package/corpus/templates/clips/.agents/skills/agent-native-toolkit/SKILL.md +133 -0
  34. package/corpus/templates/clips/AGENTS.md +5 -0
  35. package/corpus/templates/content/.agents/skills/agent-native-toolkit/SKILL.md +133 -0
  36. package/corpus/templates/content/AGENTS.md +7 -0
  37. package/corpus/templates/content/actions/_content-database-personal-view.ts +49 -5
  38. package/corpus/templates/content/actions/_content-document-access.ts +21 -0
  39. package/corpus/templates/content/actions/_content-favorites.ts +91 -0
  40. package/corpus/templates/content/actions/_content-files.ts +79 -23
  41. package/corpus/templates/content/actions/_content-sidebar-state.ts +24 -0
  42. package/corpus/templates/content/actions/_content-space-access.ts +15 -17
  43. package/corpus/templates/content/actions/_content-space-catalog-guards.ts +20 -0
  44. package/corpus/templates/content/actions/_content-spaces.ts +364 -167
  45. package/corpus/templates/content/actions/_database-source-utils.ts +8 -0
  46. package/corpus/templates/content/actions/_database-utils.ts +249 -15
  47. package/corpus/templates/content/actions/_delete-content-space.ts +84 -0
  48. package/corpus/templates/content/actions/_files-system-properties.ts +499 -0
  49. package/corpus/templates/content/actions/_local-file-documents.ts +0 -1
  50. package/corpus/templates/content/actions/_property-utils.ts +18 -9
  51. package/corpus/templates/content/actions/bind-content-database-source-field.ts +3 -0
  52. package/corpus/templates/content/actions/configure-document-property.ts +3 -0
  53. package/corpus/templates/content/actions/create-content-database.ts +2 -2
  54. package/corpus/templates/content/actions/create-content-space.ts +34 -0
  55. package/corpus/templates/content/actions/create-document.ts +4 -1
  56. package/corpus/templates/content/actions/delete-content-space.ts +19 -0
  57. package/corpus/templates/content/actions/delete-database-items.ts +28 -1
  58. package/corpus/templates/content/actions/delete-document-property.ts +3 -0
  59. package/corpus/templates/content/actions/delete-document.ts +39 -2
  60. package/corpus/templates/content/actions/duplicate-database-item.ts +6 -0
  61. package/corpus/templates/content/actions/duplicate-database-items.ts +6 -0
  62. package/corpus/templates/content/actions/duplicate-document-property.ts +3 -0
  63. package/corpus/templates/content/actions/get-content-database.ts +11 -2
  64. package/corpus/templates/content/actions/get-content-sidebar-state.ts +23 -0
  65. package/corpus/templates/content/actions/get-document.ts +32 -7
  66. package/corpus/templates/content/actions/import-content-source.ts +33 -1
  67. package/corpus/templates/content/actions/list-content-spaces.ts +34 -2
  68. package/corpus/templates/content/actions/list-documents.ts +39 -7
  69. package/corpus/templates/content/actions/reorder-document-property.ts +9 -0
  70. package/corpus/templates/content/actions/set-document-property.ts +41 -23
  71. package/corpus/templates/content/actions/share-local-file-document.ts +15 -0
  72. package/corpus/templates/content/actions/submit-content-database-form.ts +7 -0
  73. package/corpus/templates/content/actions/sync-local-folder-source.ts +14 -0
  74. package/corpus/templates/content/actions/update-content-database-view.ts +1 -0
  75. package/corpus/templates/content/actions/update-content-sidebar-state.ts +27 -0
  76. package/corpus/templates/content/actions/update-document.ts +158 -66
  77. package/corpus/templates/content/app/components/EmptyState.tsx +4 -57
  78. package/corpus/templates/content/app/components/editor/CommentsSidebar.tsx +8 -1
  79. package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +519 -178
  80. package/corpus/templates/content/app/components/editor/DocumentInfoPanel.tsx +39 -0
  81. package/corpus/templates/content/app/components/editor/DocumentProperties.tsx +1 -1
  82. package/corpus/templates/content/app/components/editor/DocumentToolbar.tsx +266 -7
  83. package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +430 -152
  84. package/corpus/templates/content/app/components/editor/database/sidebar.tsx +467 -66
  85. package/corpus/templates/content/app/components/editor/database/view-config.ts +9 -3
  86. package/corpus/templates/content/app/components/sidebar/DocumentSidebar.tsx +720 -196
  87. package/corpus/templates/content/app/components/sidebar/DocumentTreeItem.tsx +173 -1
  88. package/corpus/templates/content/app/components/sidebar/document-sidebar-sections.ts +1 -1
  89. package/corpus/templates/content/app/components/sidebar/select-content-space.ts +91 -16
  90. package/corpus/templates/content/app/entry.client.tsx +5 -1
  91. package/corpus/templates/content/app/hooks/use-content-database.ts +84 -3
  92. package/corpus/templates/content/app/hooks/use-content-spaces.ts +60 -2
  93. package/corpus/templates/content/app/hooks/use-create-page.ts +30 -0
  94. package/corpus/templates/content/app/hooks/use-documents.ts +270 -19
  95. package/corpus/templates/content/app/i18n-data.ts +243 -20
  96. package/corpus/templates/content/app/lib/local-folder-picker-safety.ts +54 -0
  97. package/corpus/templates/content/app/lib/local-folder-picker-support.ts +18 -0
  98. package/corpus/templates/content/app/routes/_app.favorites.tsx +30 -0
  99. package/corpus/templates/content/app/routes/_app.local-files.tsx +18 -6
  100. package/corpus/templates/content/changelog/2026-07-17-workspace-files-now-stay-in-a-loading-state-until-automatic-.md +6 -0
  101. package/corpus/templates/content/changelog/2026-07-17-workspace-switching-now-opens-the-selected-files-database-pr.md +6 -0
  102. package/corpus/templates/content/changelog/2026-07-17-workspaces-now-expand-above-their-own-files-list-without-dup.md +6 -0
  103. package/corpus/templates/content/changelog/2026-07-18-create-named-workspaces-each-with-its-own-private-files-data.md +6 -0
  104. package/corpus/templates/content/changelog/2026-07-18-favorites-align-with-workspace-and-file-rows-in-the-sidebar.md +6 -0
  105. package/corpus/templates/content/changelog/2026-07-18-local-folder-selection-no-longer-invokes-the-unsafe-native-p.md +6 -0
  106. package/corpus/templates/content/changelog/2026-07-18-made-content-workspaces-independently-expandable-restored-or.md +6 -0
  107. package/corpus/templates/content/changelog/2026-07-18-page-details-now-live-in-a-shared-info-and-comments-rail-dat.md +6 -0
  108. package/corpus/templates/content/changelog/2026-07-18-workspace-database-views-now-control-sidebar-workspace-navig.md +6 -0
  109. package/corpus/templates/content/changelog/2026-07-18-workspace-navigation-now-aligns-favorites-with-workspace-pag.md +6 -0
  110. package/corpus/templates/content/changelog/2026-07-19-favorites-now-open-as-a-table-and-compact-breadcrumbs-make-w.md +6 -0
  111. package/corpus/templates/content/changelog/2026-07-19-files-databases-now-start-unfiltered-sidebar-rows-exactly-fo.md +6 -0
  112. package/corpus/templates/content/changelog/2026-07-19-sidebar-page-trees-now-open-only-when-expanded-remember-thei.md +6 -0
  113. package/corpus/templates/content/changelog/2026-07-19-workspace-file-databases-now-use-the-workspace-name-open-as-.md +6 -0
  114. package/corpus/templates/content/changelog/2026-07-19-workspace-navigation-now-stays-stable-on-hover-uses-folder-i.md +6 -0
  115. package/corpus/templates/content/changelog/2026-07-19-workspace-rows-now-use-folder-icons-and-deleting-a-user-crea.md +6 -0
  116. package/corpus/templates/content/changelog/2026-07-19-workspaces-can-be-created-without-leaving-the-workspaces-dat.md +6 -0
  117. package/corpus/templates/content/e2e/global-setup.ts +3 -2
  118. package/corpus/templates/content/parity/matrix.md +28 -28
  119. package/corpus/templates/content/parity/matrix.ts +8 -2
  120. package/corpus/templates/content/server/db/index.ts +1 -0
  121. package/corpus/templates/content/server/db/schema.ts +10 -0
  122. package/corpus/templates/content/server/lib/documents.ts +12 -7
  123. package/corpus/templates/content/server/plugins/db.ts +17 -0
  124. package/corpus/templates/content/shared/api.ts +9 -0
  125. package/corpus/templates/content/shared/database-form.ts +11 -2
  126. package/corpus/templates/content/vite.config.ts +1 -0
  127. package/corpus/templates/design/.agents/skills/agent-native-toolkit/SKILL.md +133 -0
  128. package/corpus/templates/design/AGENTS.md +5 -0
  129. package/corpus/templates/dispatch/.agents/skills/agent-native-toolkit/SKILL.md +133 -0
  130. package/corpus/templates/dispatch/AGENTS.md +5 -0
  131. package/corpus/templates/forms/.agents/skills/agent-native-toolkit/SKILL.md +133 -0
  132. package/corpus/templates/forms/AGENTS.md +5 -0
  133. package/corpus/templates/macros/.agents/skills/agent-native-toolkit/SKILL.md +133 -0
  134. package/corpus/templates/macros/AGENTS.md +5 -0
  135. package/corpus/templates/mail/.agents/skills/agent-native-toolkit/SKILL.md +133 -0
  136. package/corpus/templates/mail/AGENTS.md +5 -0
  137. package/corpus/templates/plan/.agents/skills/agent-native-toolkit/SKILL.md +133 -0
  138. package/corpus/templates/plan/AGENTS.md +5 -0
  139. package/corpus/templates/slides/.agents/skills/agent-native-toolkit/SKILL.md +133 -0
  140. package/corpus/templates/slides/AGENTS.md +5 -0
  141. package/corpus/templates/tasks/.agents/skills/agent-native-toolkit/SKILL.md +133 -0
  142. package/corpus/templates/tasks/AGENTS.md +5 -0
  143. package/dist/cli/create.d.ts.map +1 -1
  144. package/dist/cli/create.js +21 -0
  145. package/dist/cli/create.js.map +1 -1
  146. package/dist/cli/skills.d.ts.map +1 -1
  147. package/dist/cli/skills.js +19 -0
  148. package/dist/cli/skills.js.map +1 -1
  149. package/dist/client/build-compatibility.d.ts +11 -0
  150. package/dist/client/build-compatibility.d.ts.map +1 -0
  151. package/dist/client/build-compatibility.js +56 -0
  152. package/dist/client/build-compatibility.js.map +1 -0
  153. package/dist/client/route-chunk-recovery/index.d.ts +1 -0
  154. package/dist/client/route-chunk-recovery/index.d.ts.map +1 -1
  155. package/dist/client/route-chunk-recovery/index.js +1 -0
  156. package/dist/client/route-chunk-recovery/index.js.map +1 -1
  157. package/dist/client/use-action.d.ts.map +1 -1
  158. package/dist/client/use-action.js +19 -1
  159. package/dist/client/use-action.js.map +1 -1
  160. package/dist/collab/routes.d.ts +1 -1
  161. package/dist/deploy/build.js +2 -1
  162. package/dist/deploy/build.js.map +1 -1
  163. package/dist/notifications/routes.d.ts +1 -1
  164. package/dist/observability/routes.d.ts +3 -3
  165. package/dist/org/context.d.ts +4 -2
  166. package/dist/org/context.d.ts.map +1 -1
  167. package/dist/org/context.js +44 -22
  168. package/dist/org/context.js.map +1 -1
  169. package/dist/progress/routes.d.ts +1 -1
  170. package/dist/provider-api/actions/custom-provider-registration.d.ts +10 -10
  171. package/dist/provider-api/actions/provider-api.d.ts +12 -12
  172. package/dist/provider-api/corpus-jobs.d.ts +2 -2
  173. package/dist/resources/handlers.d.ts +1 -1
  174. package/dist/server/action-routes.d.ts.map +1 -1
  175. package/dist/server/action-routes.js +32 -2
  176. package/dist/server/action-routes.js.map +1 -1
  177. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  178. package/dist/server/auth-plugin.d.ts.map +1 -1
  179. package/dist/server/auth-plugin.js +9 -5
  180. package/dist/server/auth-plugin.js.map +1 -1
  181. package/dist/server/better-auth-instance.d.ts.map +1 -1
  182. package/dist/server/better-auth-instance.js +4 -1
  183. package/dist/server/better-auth-instance.js.map +1 -1
  184. package/dist/server/transcribe-voice.d.ts +1 -1
  185. package/dist/shared/mcp-embed-headers.d.ts +1 -1
  186. package/dist/shared/mcp-embed-headers.d.ts.map +1 -1
  187. package/dist/shared/mcp-embed-headers.js +1 -1
  188. package/dist/shared/mcp-embed-headers.js.map +1 -1
  189. package/dist/templates/chat/.agents/skills/agent-native-toolkit/SKILL.md +133 -0
  190. package/dist/templates/chat/AGENTS.md +4 -1
  191. package/dist/templates/default/.agents/skills/agent-native-toolkit/SKILL.md +133 -0
  192. package/dist/templates/default/AGENTS.md +4 -1
  193. package/dist/templates/headless/.agents/skills/agent-native-toolkit/SKILL.md +133 -0
  194. package/dist/templates/headless/AGENTS.md +5 -1
  195. package/dist/templates/workspace-core/.agents/skills/agent-native-toolkit/SKILL.md +26 -0
  196. package/dist/templates/workspace-core/AGENTS.md +3 -1
  197. package/dist/templates/workspace-root/AGENTS.md +3 -1
  198. package/dist/vite/client.d.ts +6 -0
  199. package/dist/vite/client.d.ts.map +1 -1
  200. package/dist/vite/client.js +8 -0
  201. package/dist/vite/client.js.map +1 -1
  202. package/package.json +1 -1
  203. package/src/cli/create.ts +23 -0
  204. package/src/cli/skills.ts +24 -0
  205. package/src/client/build-compatibility.ts +77 -0
  206. package/src/client/route-chunk-recovery/index.ts +1 -0
  207. package/src/client/use-action.ts +32 -1
  208. package/src/deploy/build.ts +4 -1
  209. package/src/org/context.ts +56 -30
  210. package/src/server/action-routes.ts +52 -2
  211. package/src/server/auth-plugin.ts +9 -4
  212. package/src/server/better-auth-instance.ts +5 -1
  213. package/src/shared/mcp-embed-headers.ts +1 -1
  214. package/src/templates/chat/.agents/skills/agent-native-toolkit/SKILL.md +133 -0
  215. package/src/templates/chat/AGENTS.md +4 -1
  216. package/src/templates/default/.agents/skills/agent-native-toolkit/SKILL.md +133 -0
  217. package/src/templates/default/AGENTS.md +4 -1
  218. package/src/templates/headless/.agents/skills/agent-native-toolkit/SKILL.md +133 -0
  219. package/src/templates/headless/AGENTS.md +5 -1
  220. package/src/templates/workspace-core/.agents/skills/agent-native-toolkit/SKILL.md +26 -0
  221. package/src/templates/workspace-core/AGENTS.md +3 -1
  222. package/src/templates/workspace-root/AGENTS.md +3 -1
  223. package/src/vite/client.ts +17 -0
@@ -86,6 +86,7 @@ import {
86
86
  organizationContentSpaceId,
87
87
  provisionContentSpaces,
88
88
  } from "./_content-spaces.js";
89
+ import { ensureFilesSystemPropertyDefinitions } from "./_files-system-properties.js";
89
90
  import {
90
91
  databaseItemsPositionScope,
91
92
  documentsPositionScope,
@@ -6258,6 +6259,13 @@ export async function ensureDatabaseSourceProperty(args: {
6258
6259
  database: ContentDatabaseRow;
6259
6260
  now: string;
6260
6261
  }) {
6262
+ if (args.database.systemRole === "files") {
6263
+ await ensureFilesSystemPropertyDefinitions({
6264
+ database: args.database,
6265
+ now: args.now,
6266
+ });
6267
+ return;
6268
+ }
6261
6269
  const db = getDb();
6262
6270
  const sources = await db
6263
6271
  .select({
@@ -1,8 +1,18 @@
1
- import { and, asc, eq, inArray, isNull, sql } from "drizzle-orm";
1
+ import {
2
+ getRequestOrgId,
3
+ getRequestUserEmail,
4
+ } from "@agent-native/core/server/request-context";
5
+ import {
6
+ accessFilter,
7
+ ROLE_RANK,
8
+ type ShareRole,
9
+ } from "@agent-native/core/sharing";
10
+ import { and, asc, eq, inArray, isNull, or, sql } from "drizzle-orm";
2
11
 
3
12
  import { getDb, schema } from "../server/db/index.js";
4
13
  import { getDocumentContextPath } from "../server/lib/document-context.js";
5
14
  import {
15
+ documentDiscoveryFilter,
6
16
  parseDocumentFavorite,
7
17
  parseDocumentHideFromSearch,
8
18
  } from "../server/lib/documents.js";
@@ -11,11 +21,17 @@ import type {
11
21
  ContentDatabaseMembership,
12
22
  ContentDatabaseResponse,
13
23
  } from "../shared/api.js";
24
+ import { favoriteDocumentIds } from "./_content-favorites.js";
25
+ import { listContentOrganizationMemberships } from "./_content-space-access.js";
14
26
  import { getAllContentDatabaseSourceSnapshots } from "./_database-source-utils.js";
15
27
  import {
16
28
  applyFederatedOverlayValues,
17
29
  federateSources,
18
30
  } from "./_federation-join.js";
31
+ import {
32
+ applyFilesSystemPropertyProjection,
33
+ filesSystemPropertyProjection,
34
+ } from "./_files-system-properties.js";
19
35
  import {
20
36
  listPropertiesForDatabaseDocuments,
21
37
  listPropertiesForDatabase,
@@ -29,6 +45,15 @@ function canManageRole(role: string) {
29
45
  return role === "owner" || role === "admin";
30
46
  }
31
47
 
48
+ function canEditRole(role: string) {
49
+ return role === "owner" || role === "admin" || role === "editor";
50
+ }
51
+
52
+ function strongerRole(current: ShareRole | null, next: ShareRole): ShareRole {
53
+ if (!current || ROLE_RANK[next] > ROLE_RANK[current]) return next;
54
+ return current;
55
+ }
56
+
32
57
  type DatabaseMembershipRow = {
33
58
  item: typeof schema.contentDatabaseItems.$inferSelect;
34
59
  database: typeof schema.contentDatabases.$inferSelect;
@@ -195,10 +220,34 @@ export function filterContentDatabaseSourceRowsForPage<
195
220
  );
196
221
  }
197
222
 
223
+ export function filterContentDatabaseSourceForVisibleDocuments<
224
+ TSource extends {
225
+ rows: Array<{ documentId: string }>;
226
+ changeSets: Array<{ documentId: string | null }>;
227
+ },
228
+ >(source: TSource, visibleDocumentIds: ReadonlySet<string>): TSource {
229
+ return {
230
+ ...source,
231
+ rows: source.rows.filter(
232
+ (row) => !row.documentId || visibleDocumentIds.has(row.documentId),
233
+ ),
234
+ changeSets: source.changeSets.filter(
235
+ (changeSet) =>
236
+ !changeSet.documentId || visibleDocumentIds.has(changeSet.documentId),
237
+ ),
238
+ };
239
+ }
240
+
198
241
  function serializeDocument(
199
242
  doc: DocumentListRow,
200
243
  membership?: DatabaseMembershipRow,
244
+ shareRole?: ShareRole,
245
+ isFavorite?: boolean,
201
246
  ) {
247
+ const isOwner =
248
+ doc.ownerEmail.trim().toLowerCase() ===
249
+ getRequestUserEmail()?.trim().toLowerCase();
250
+ const accessRole = isOwner ? ("owner" as const) : (shareRole ?? "viewer");
202
251
  return {
203
252
  id: doc.id,
204
253
  parentId: doc.parentId,
@@ -209,12 +258,12 @@ function serializeDocument(
209
258
  description: doc.description,
210
259
  icon: doc.icon,
211
260
  position: doc.position,
212
- isFavorite: parseDocumentFavorite(doc.isFavorite),
261
+ isFavorite: isFavorite ?? parseDocumentFavorite(doc.isFavorite),
213
262
  hideFromSearch: parseDocumentHideFromSearch(doc.hideFromSearch),
214
263
  visibility: doc.visibility,
215
- accessRole: "owner" as const,
216
- canEdit: true,
217
- canManage: canManageRole("owner"),
264
+ accessRole,
265
+ canEdit: canEditRole(accessRole),
266
+ canManage: canManageRole(accessRole),
218
267
  databaseMembership: membership
219
268
  ? serializeDatabaseMembership(membership)
220
269
  : undefined,
@@ -250,15 +299,77 @@ export async function getContentDatabaseResponse(
250
299
  // shared one a viewer is opening) must not mutate schema.
251
300
 
252
301
  const { limit, offset } = normalizeContentDatabasePageOptions(options);
302
+ const userEmail = getRequestUserEmail();
303
+ const activeOrgId = getRequestOrgId();
304
+ const authorizedOrgIds =
305
+ database.systemRole === "favorites" && userEmail
306
+ ? [
307
+ ...new Set([
308
+ ...(await listContentOrganizationMemberships(userEmail)).map(
309
+ (membership) => membership.orgId,
310
+ ),
311
+ ...(activeOrgId ? [activeOrgId] : []),
312
+ ]),
313
+ ]
314
+ : [];
315
+ const favoritesVisibleDocumentIds =
316
+ database.systemRole === "favorites" && userEmail
317
+ ? (
318
+ await db
319
+ .select({ id: schema.documents.id })
320
+ .from(schema.documents)
321
+ .where(
322
+ and(
323
+ or(
324
+ accessFilter(schema.documents, schema.documentShares, {
325
+ userEmail,
326
+ }),
327
+ ...authorizedOrgIds.map((orgId) =>
328
+ accessFilter(schema.documents, schema.documentShares, {
329
+ userEmail,
330
+ orgId,
331
+ }),
332
+ ),
333
+ ),
334
+ documentDiscoveryFilter({
335
+ userEmail,
336
+ orgIds: authorizedOrgIds,
337
+ }),
338
+ ),
339
+ )
340
+ ).map((document) => document.id)
341
+ : null;
342
+ const organizationFilesItemFilter =
343
+ database.systemRole === "files" && database.orgId
344
+ ? sql`exists (
345
+ select 1 from ${schema.documents}
346
+ where ${schema.documents.id} = ${schema.contentDatabaseItems.documentId}
347
+ and ${schema.documents.orgId} = ${database.orgId}
348
+ and ${schema.documents.visibility} in ('org', 'public')
349
+ and (${schema.documents.hideFromSearch} = 0 or ${schema.documents.hideFromSearch} is null)
350
+ )`
351
+ : undefined;
352
+ const visibleItemFilter = and(
353
+ eq(schema.contentDatabaseItems.databaseId, databaseId),
354
+ organizationFilesItemFilter,
355
+ favoritesVisibleDocumentIds
356
+ ? favoritesVisibleDocumentIds.length > 0
357
+ ? inArray(
358
+ schema.contentDatabaseItems.documentId,
359
+ favoritesVisibleDocumentIds,
360
+ )
361
+ : sql`1 = 0`
362
+ : undefined,
363
+ );
253
364
  const [itemCount] = await db
254
365
  .select({ count: sql<number>`COUNT(*)` })
255
366
  .from(schema.contentDatabaseItems)
256
- .where(eq(schema.contentDatabaseItems.databaseId, databaseId));
367
+ .where(visibleItemFilter);
257
368
 
258
369
  let itemsQuery = db
259
370
  .select()
260
371
  .from(schema.contentDatabaseItems)
261
- .where(eq(schema.contentDatabaseItems.databaseId, databaseId))
372
+ .where(visibleItemFilter)
262
373
  .orderBy(asc(schema.contentDatabaseItems.position))
263
374
  .$dynamic();
264
375
  if (limit !== null) {
@@ -277,17 +388,116 @@ export async function getContentDatabaseResponse(
277
388
  schema.documents.id,
278
389
  items.map((item) => item.documentId),
279
390
  ),
280
- eq(schema.documents.ownerEmail, database.ownerEmail),
391
+ database.systemRole === "favorites"
392
+ ? favoritesVisibleDocumentIds?.length
393
+ ? inArray(schema.documents.id, favoritesVisibleDocumentIds)
394
+ : sql`1 = 0`
395
+ : database.systemRole === "files" && database.orgId
396
+ ? and(
397
+ eq(schema.documents.orgId, database.orgId),
398
+ or(
399
+ eq(schema.documents.visibility, "org"),
400
+ eq(schema.documents.visibility, "public"),
401
+ ),
402
+ or(
403
+ eq(schema.documents.hideFromSearch, 0),
404
+ isNull(schema.documents.hideFromSearch),
405
+ ),
406
+ )
407
+ : eq(schema.documents.ownerEmail, database.ownerEmail),
281
408
  ),
282
409
  )
283
410
  : [];
284
411
  const documentById = new Map(documents.map((doc) => [doc.id, doc]));
412
+ const favorites =
413
+ database.systemRole === "favorites"
414
+ ? new Set(documents.map((document) => document.id))
415
+ : userEmail
416
+ ? await favoriteDocumentIds(
417
+ db,
418
+ userEmail,
419
+ documents.map((document) => document.id),
420
+ )
421
+ : new Set<string>();
422
+ const shareRoleByDocumentId = new Map<string, ShareRole>();
423
+ if (documents.length > 0) {
424
+ const principalClauses: NonNullable<ReturnType<typeof and>>[] = [];
425
+ const userEmail = getRequestUserEmail();
426
+ const orgId = getRequestOrgId();
427
+ if (userEmail) {
428
+ principalClauses.push(
429
+ and(
430
+ eq(schema.documentShares.principalType, "user"),
431
+ eq(schema.documentShares.principalId, userEmail),
432
+ )!,
433
+ );
434
+ }
435
+ if (orgId) {
436
+ principalClauses.push(
437
+ and(
438
+ eq(schema.documentShares.principalType, "org"),
439
+ eq(schema.documentShares.principalId, orgId),
440
+ )!,
441
+ );
442
+ }
443
+ const shareRows =
444
+ principalClauses.length > 0
445
+ ? await db
446
+ .select({
447
+ resourceId: schema.documentShares.resourceId,
448
+ role: schema.documentShares.role,
449
+ })
450
+ .from(schema.documentShares)
451
+ .where(
452
+ and(
453
+ inArray(
454
+ schema.documentShares.resourceId,
455
+ documents.map((document) => document.id),
456
+ ),
457
+ or(...principalClauses),
458
+ ),
459
+ )
460
+ : [];
461
+ for (const row of shareRows) {
462
+ shareRoleByDocumentId.set(
463
+ row.resourceId,
464
+ strongerRole(
465
+ shareRoleByDocumentId.get(row.resourceId) ?? null,
466
+ row.role,
467
+ ),
468
+ );
469
+ }
470
+ }
285
471
  const propertiesByDocumentId = await listPropertiesForDatabaseDocuments(
286
472
  databaseId,
287
473
  // Property serialization uses metadata only; this list projection carries
288
474
  // every document field it consumes except the deliberately omitted body.
289
475
  documents as Array<typeof schema.documents.$inferSelect>,
290
476
  );
477
+ const databaseProperties = await listPropertiesForDatabase(databaseId);
478
+ const filesProjection = await filesSystemPropertyProjection({
479
+ database,
480
+ documents,
481
+ properties: databaseProperties,
482
+ });
483
+ const responseProperties = filesProjection
484
+ ? applyFilesSystemPropertyProjection({
485
+ properties: databaseProperties,
486
+ projection: filesProjection,
487
+ })
488
+ : databaseProperties;
489
+ if (filesProjection) {
490
+ for (const document of documents) {
491
+ propertiesByDocumentId.set(
492
+ document.id,
493
+ applyFilesSystemPropertyProjection({
494
+ properties: propertiesByDocumentId.get(document.id) ?? [],
495
+ projection: filesProjection,
496
+ documentId: document.id,
497
+ }),
498
+ );
499
+ }
500
+ }
291
501
  const queuedBodyHydrationItemIds =
292
502
  items.length > 0
293
503
  ? new Set(
@@ -316,11 +526,16 @@ export async function getContentDatabaseResponse(
316
526
  serializedItems.push({
317
527
  id: item.id,
318
528
  databaseId: item.databaseId,
319
- document: serializeDocument(document, {
320
- item,
321
- database,
322
- bodyHydrationQueueId: bodyHydrationQueued ? item.id : null,
323
- }),
529
+ document: serializeDocument(
530
+ document,
531
+ {
532
+ item,
533
+ database,
534
+ bodyHydrationQueueId: bodyHydrationQueued ? item.id : null,
535
+ },
536
+ shareRoleByDocumentId.get(document.id),
537
+ favorites.has(document.id),
538
+ ),
324
539
  position: item.position,
325
540
  bodyHydration: serializeBodyHydration(item, {
326
541
  queued: bodyHydrationQueued,
@@ -329,7 +544,25 @@ export async function getContentDatabaseResponse(
329
544
  });
330
545
  }
331
546
 
332
- const sources = await getAllContentDatabaseSourceSnapshots(database);
547
+ const sourceSnapshots = await getAllContentDatabaseSourceSnapshots(database);
548
+ const organizationVisibleDocumentIds = organizationFilesItemFilter
549
+ ? new Set(
550
+ (
551
+ await db
552
+ .select({ documentId: schema.contentDatabaseItems.documentId })
553
+ .from(schema.contentDatabaseItems)
554
+ .where(visibleItemFilter)
555
+ ).map((item) => item.documentId),
556
+ )
557
+ : null;
558
+ const sources = organizationVisibleDocumentIds
559
+ ? sourceSnapshots.map((source) =>
560
+ filterContentDatabaseSourceForVisibleDocuments(
561
+ source,
562
+ organizationVisibleDocumentIds,
563
+ ),
564
+ )
565
+ : sourceSnapshots;
333
566
  const serializedDocumentIds = new Set(
334
567
  serializedItems.map((item) => item.document.id),
335
568
  );
@@ -365,7 +598,7 @@ export async function getContentDatabaseResponse(
365
598
  contextPath: databaseDocument
366
599
  ? await getDocumentContextPath(databaseDocument)
367
600
  : [],
368
- properties: await listPropertiesForDatabase(databaseId),
601
+ properties: responseProperties,
369
602
  items: itemsWithOverlay,
370
603
  source: pagedPrimary,
371
604
  sources: pagedSources,
@@ -467,6 +700,7 @@ export async function getDatabaseItemByDocumentId(
467
700
  .orderBy(
468
701
  sql`CASE WHEN ${schema.contentDatabaseSourceRows.sourceId} IS NOT NULL THEN 0 ELSE 1 END`,
469
702
  sql`CASE WHEN ${schema.contentDatabases.systemRole} IS NULL THEN 0 ELSE 1 END`,
703
+ sql`CASE WHEN ${schema.contentDatabases.systemRole} = 'files' THEN 0 ELSE 1 END`,
470
704
  asc(schema.contentDatabases.id),
471
705
  );
472
706
  return row ?? null;
@@ -0,0 +1,84 @@
1
+ import { and, eq } from "drizzle-orm";
2
+
3
+ import { getDb, schema } from "../server/db/index.js";
4
+ import { resolveContentSpaceAccess } from "./_content-space-access.js";
5
+ import { deleteDocumentRecursive } from "./delete-document.js";
6
+
7
+ type Db = ReturnType<typeof getDb>;
8
+
9
+ export async function deleteUserContentSpace(db: Db, spaceId: string) {
10
+ return db.transaction(async (tx) => {
11
+ const scopedDb = tx as unknown as Db;
12
+ const access = await resolveContentSpaceAccess(spaceId, "editor", {
13
+ db: scopedDb,
14
+ });
15
+ if (access.role !== "owner" || access.space.kind !== "user") {
16
+ throw new Error("Only user-created workspaces can be deleted");
17
+ }
18
+
19
+ const [mapping] = await scopedDb
20
+ .select()
21
+ .from(schema.contentSpaceCatalogItems)
22
+ .where(
23
+ and(
24
+ eq(schema.contentSpaceCatalogItems.spaceId, spaceId),
25
+ eq(
26
+ schema.contentSpaceCatalogItems.ownerEmail,
27
+ access.authority.userEmail,
28
+ ),
29
+ ),
30
+ );
31
+ if (!mapping) throw new Error("Workspace catalog entry not found");
32
+
33
+ const [filesDatabase] = await scopedDb
34
+ .select({ documentId: schema.contentDatabases.documentId })
35
+ .from(schema.contentDatabases)
36
+ .where(
37
+ and(
38
+ eq(schema.contentDatabases.id, access.space.filesDatabaseId),
39
+ eq(schema.contentDatabases.spaceId, spaceId),
40
+ eq(schema.contentDatabases.systemRole, "files"),
41
+ ),
42
+ );
43
+ if (!filesDatabase) throw new Error("Workspace Files database not found");
44
+
45
+ await scopedDb
46
+ .delete(schema.contentSpaceCatalogItems)
47
+ .where(eq(schema.contentSpaceCatalogItems.id, mapping.id));
48
+ const deletedCatalogDocuments = await deleteDocumentRecursive(
49
+ scopedDb,
50
+ mapping.documentId,
51
+ access.space.ownerEmail,
52
+ );
53
+ const deletedWorkspaceDocuments = await deleteDocumentRecursive(
54
+ scopedDb,
55
+ filesDatabase.documentId,
56
+ access.space.ownerEmail,
57
+ );
58
+
59
+ const remainingDocuments = await scopedDb
60
+ .select({ id: schema.documents.id })
61
+ .from(schema.documents)
62
+ .where(
63
+ and(
64
+ eq(schema.documents.spaceId, spaceId),
65
+ eq(schema.documents.ownerEmail, access.space.ownerEmail),
66
+ ),
67
+ );
68
+ if (remainingDocuments.length > 0) {
69
+ throw new Error(
70
+ "Workspace contains content outside its canonical Files database",
71
+ );
72
+ }
73
+
74
+ await scopedDb
75
+ .delete(schema.contentSpaces)
76
+ .where(eq(schema.contentSpaces.id, spaceId));
77
+
78
+ return {
79
+ spaceId,
80
+ deletedDocuments:
81
+ deletedCatalogDocuments.length + deletedWorkspaceDocuments.length,
82
+ };
83
+ });
84
+ }