@agent-native/core 0.176.5-nightly-20260903175350 → 0.176.5

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 (29) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/calendar/CHANGELOG.md +18 -0
  3. package/corpus/templates/clips/app/components/recorder/storage-setup-card.tsx +13 -7
  4. package/corpus/templates/content/CHANGELOG.md +1 -0
  5. package/corpus/templates/content/actions/_collection-export.ts +925 -0
  6. package/corpus/templates/content/actions/export-document.ts +79 -255
  7. package/corpus/templates/content/app/components/editor/DocumentToolbar.tsx +39 -38
  8. package/corpus/templates/content/app/components/editor/database/DatabaseExportDialog.tsx +444 -113
  9. package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +4 -0
  10. package/corpus/templates/content/app/i18n-data.ts +99 -0
  11. package/corpus/templates/content/docs/product/capabilities/content.portability.collection-export.md +139 -0
  12. package/corpus/templates/content/docs/product/encyclopedia.md +12 -8
  13. package/corpus/templates/content/docs/product/features/content.feature.take-the-whole-vault-with-you.md +5 -1
  14. package/corpus/templates/content/docs/product/roadmap.md +1 -1
  15. package/corpus/templates/content/shared/database-collection-export.ts +161 -0
  16. package/corpus/templates/content/shared/database-csv-export.ts +6 -3
  17. package/corpus/templates/design/CHANGELOG.md +1 -0
  18. package/corpus/templates/factory/CHANGELOG.md +20 -0
  19. package/corpus/templates/mail/CHANGELOG.md +12 -0
  20. package/corpus/templates/plan/CHANGELOG.md +6 -0
  21. package/corpus/templates/slides/CHANGELOG.md +9 -0
  22. package/dist/collab/awareness.d.ts +2 -2
  23. package/dist/collab/struct-routes.d.ts +1 -1
  24. package/dist/notifications/routes.d.ts +1 -1
  25. package/dist/observability/routes.d.ts +5 -5
  26. package/dist/progress/routes.d.ts +1 -1
  27. package/dist/secrets/routes.d.ts +9 -9
  28. package/dist/server/realtime-token.d.ts +1 -1
  29. package/package.json +3 -3
@@ -1,41 +1,22 @@
1
1
  import { defineAction } from "@agent-native/core/action";
2
2
  import { buildDeepLink } from "@agent-native/core/server";
3
- import { getRequestUserEmail } from "@agent-native/core/server/request-context";
4
- import { accessFilter, resolveAccess } from "@agent-native/core/sharing";
5
- import { and, asc, eq, isNull, or } from "drizzle-orm";
3
+ import { resolveAccess } from "@agent-native/core/sharing";
6
4
  import { z } from "zod";
7
5
 
8
- import { getDb, schema } from "../server/db/index.js";
9
- import type {
10
- ContentDatabaseItem,
11
- ContentDatabaseTableQuery,
12
- } from "../shared/api.js";
13
6
  import { blocksContentHash } from "../shared/blocks-field-identity.js";
14
- import { renderDatabaseCsv } from "../shared/database-csv-export.js";
15
- import { applyContentDatabaseTableQuery } from "../shared/database-query.js";
16
7
  import {
17
- buildDocumentExport,
18
- collectionItemsMarkdown,
19
- type CollectionExportItem,
20
- } from "../shared/document-export.js";
8
+ renderCollectionCsv,
9
+ renderCollectionHtml,
10
+ renderCollectionMarkdownArchive,
11
+ } from "../shared/database-collection-export.js";
12
+ import { buildDocumentExport } from "../shared/document-export.js";
21
13
  import {
22
14
  isBlocksPropertyType,
23
15
  isPrimaryBlocksField,
24
16
  } from "../shared/properties.js";
25
- import { resolveContentDocumentAccess } from "./_content-document-access.js";
26
- import { listContentOrganizationMemberships } from "./_content-space-access.js";
27
- import {
28
- CONTENT_DATABASE_MAX_READ_LIMIT,
29
- getDatabaseByDocumentId,
30
- } from "./_database-utils.js";
31
- import {
32
- listPropertiesForAllDocumentDatabases,
33
- listPropertiesForDatabase,
34
- listPropertiesForDatabaseDocuments,
35
- parseDatabaseViewConfig,
36
- } from "./_property-utils.js";
37
-
38
- const COLLECTION_EXPORT_ACCESS_CONCURRENCY = 8;
17
+ import { buildCollectionExportProjection } from "./_collection-export.js";
18
+ import { getDatabaseByDocumentId } from "./_database-utils.js";
19
+ import { listPropertiesForAllDocumentDatabases } from "./_property-utils.js";
39
20
 
40
21
  const collectionSchema = z.object({
41
22
  scope: z.discriminatedUnion("kind", [
@@ -84,218 +65,24 @@ const collectionSchema = z.object({
84
65
  }),
85
66
  ]),
86
67
  propertyIds: z.array(z.string().min(1)).max(200),
68
+ includePrimaryBody: z.boolean().default(false),
69
+ blockPropertyIds: z.array(z.string().min(1)).max(200).default([]),
87
70
  });
88
71
 
89
72
  type CollectionExport = z.infer<typeof collectionSchema>;
90
73
 
91
- function assertValidQuery(
92
- query: ContentDatabaseTableQuery,
93
- propertyIds: ReadonlySet<string>,
94
- ) {
95
- for (const key of [...query.filters, ...query.sorts].map(({ key }) => key)) {
96
- if (key !== "name" && !propertyIds.has(key)) {
97
- throw new Error(`Unknown database property "${key}" in export query`);
98
- }
99
- }
100
- }
101
-
102
- async function databaseCsvContent(
103
- documentId: string,
104
- collection: CollectionExport,
105
- ) {
106
- const database = await getDatabaseByDocumentId(documentId);
107
- if (!database) throw new Error("CSV export requires a database document");
108
-
109
- const properties = await listPropertiesForDatabase(database.id);
110
- const propertyById = new Map(
111
- properties.map((property) => [property.definition.id, property]),
112
- );
113
- if (new Set(collection.propertyIds).size !== collection.propertyIds.length) {
114
- throw new Error("CSV export property IDs must be unique");
115
- }
116
- const selectedProperties = collection.propertyIds.map((propertyId) => {
117
- const property = propertyById.get(propertyId);
118
- if (!property) throw new Error(`Unknown database property "${propertyId}"`);
119
- return property;
120
- });
121
-
122
- const query =
123
- collection.scope.kind === "current_view" ? collection.scope.query : null;
124
- const scope = collection.scope;
125
- if (scope.kind === "current_view") {
126
- const view = parseDatabaseViewConfig(database.viewConfigJson).views.find(
127
- (candidate) => candidate.id === scope.viewId,
128
- );
129
- if (!view) throw new Error(`Database view "${scope.viewId}" not found`);
130
- assertValidQuery(query!, new Set(propertyById.keys()));
131
- }
132
- const blocksAreNeeded =
133
- selectedProperties.some((property) =>
134
- isBlocksPropertyType(property.definition.type),
135
- ) ||
136
- (!!query &&
137
- (query.search.trim().length > 0 ||
138
- [...query.filters, ...query.sorts].some((constraint) => {
139
- const property = propertyById.get(constraint.key);
140
- return !!property && isBlocksPropertyType(property.definition.type);
141
- })));
142
-
143
- const userEmail = getRequestUserEmail();
144
- const memberships = userEmail
145
- ? await listContentOrganizationMemberships(userEmail)
146
- : [];
147
- const accessClauses = [accessFilter(schema.documents, schema.documentShares)];
148
- for (const membership of memberships) {
149
- accessClauses.push(
150
- accessFilter(schema.documents, schema.documentShares, {
151
- userEmail: userEmail!,
152
- orgId: membership.orgId,
153
- }),
154
- );
155
- }
156
- const rows = await getDb()
157
- .select({
158
- item: schema.contentDatabaseItems,
159
- document: schema.documents,
160
- })
161
- .from(schema.contentDatabaseItems)
162
- .innerJoin(
163
- schema.documents,
164
- eq(schema.documents.id, schema.contentDatabaseItems.documentId),
165
- )
166
- .where(
167
- and(
168
- eq(schema.contentDatabaseItems.databaseId, database.id),
169
- isNull(schema.documents.trashedAt),
170
- or(...accessClauses),
171
- ),
172
- )
173
- .orderBy(
174
- asc(schema.contentDatabaseItems.position),
175
- asc(schema.contentDatabaseItems.createdAt),
176
- asc(schema.contentDatabaseItems.id),
177
- )
178
- .limit(CONTENT_DATABASE_MAX_READ_LIMIT + 1);
179
- if (rows.length > CONTENT_DATABASE_MAX_READ_LIMIT) {
180
- throw new Error(
181
- `CSV export supports up to ${CONTENT_DATABASE_MAX_READ_LIMIT} accessible rows.`,
182
- );
183
- }
184
- if (blocksAreNeeded) {
185
- for (const { item } of rows) {
186
- if (
187
- item.bodyHydrationStatus !== "hydrated" &&
188
- item.bodyHydrationStatus !== "unavailable"
189
- ) {
190
- throw new Error(
191
- `Database item "${item.documentId}" is not ready for export`,
192
- );
193
- }
194
- }
195
- }
196
- const documents = rows.map((row) => row.document);
197
- const propertiesByDocumentId = await listPropertiesForDatabaseDocuments(
198
- database.id,
199
- documents,
200
- );
201
- const queryItems: ContentDatabaseItem[] = rows.map((row) => ({
202
- id: row.item.id,
203
- databaseId: row.item.databaseId,
204
- document: {
205
- id: row.document.id,
206
- parentId: row.document.parentId,
207
- title: row.document.title,
208
- content: row.document.content,
209
- description: row.document.description ?? undefined,
210
- icon: row.document.icon,
211
- position: row.document.position,
212
- isFavorite: row.document.isFavorite === 1,
213
- hideFromSearch: row.document.hideFromSearch === 1,
214
- createdAt: row.document.createdAt,
215
- updatedAt: row.document.updatedAt,
216
- },
217
- position: row.item.position,
218
- properties: propertiesByDocumentId.get(row.document.id) ?? [],
219
- }));
220
- const selectedRows = query
221
- ? applyContentDatabaseTableQuery(queryItems, properties, query)
222
- : queryItems;
223
- return renderDatabaseCsv(
224
- selectedProperties.map((property) => ({
225
- id: property.definition.id,
226
- name: property.definition.name,
227
- property,
228
- })),
229
- selectedRows.map((row) => ({
230
- title: row.document.title,
231
- values: new Map(
232
- row.properties.map((property) => [
233
- property.definition.id,
234
- property.value,
235
- ]),
236
- ),
237
- })),
74
+ function exportBaseName(title: string | null | undefined) {
75
+ return (
76
+ (title || "untitled")
77
+ .replace(/[^a-z0-9]+/gi, "-")
78
+ .replace(/^-+|-+$/g, "")
79
+ .toLowerCase() || "untitled"
238
80
  );
239
81
  }
240
82
 
241
- async function databaseExportContent(documentId: string) {
242
- const database = await getDatabaseByDocumentId(documentId);
243
- if (!database) return null;
244
-
245
- const members = await getDb()
246
- .select({
247
- documentId: schema.contentDatabaseItems.documentId,
248
- bodyHydrationStatus: schema.contentDatabaseItems.bodyHydrationStatus,
249
- })
250
- .from(schema.contentDatabaseItems)
251
- .where(eq(schema.contentDatabaseItems.databaseId, database.id))
252
- .orderBy(
253
- asc(schema.contentDatabaseItems.position),
254
- asc(schema.contentDatabaseItems.createdAt),
255
- asc(schema.contentDatabaseItems.id),
256
- );
257
- const items: CollectionExportItem[] = [];
258
-
259
- for (
260
- let offset = 0;
261
- offset < members.length;
262
- offset += COLLECTION_EXPORT_ACCESS_CONCURRENCY
263
- ) {
264
- const batch = members.slice(
265
- offset,
266
- offset + COLLECTION_EXPORT_ACCESS_CONCURRENCY,
267
- );
268
- const resolved = await Promise.all(
269
- batch.map(async (member) => ({
270
- member,
271
- access: await resolveContentDocumentAccess(member.documentId),
272
- })),
273
- );
274
-
275
- for (const { member, access } of resolved) {
276
- if (!access || access.resource.trashedAt) continue;
277
- if (
278
- member.bodyHydrationStatus !== "hydrated" &&
279
- member.bodyHydrationStatus !== "unavailable"
280
- ) {
281
- throw new Error(
282
- `Database item "${member.documentId}" is not ready for export`,
283
- );
284
- }
285
-
286
- items.push({
287
- title: access.resource.title,
288
- content: access.resource.content,
289
- });
290
- }
291
- }
292
-
293
- return collectionItemsMarkdown(items);
294
- }
295
-
296
83
  export default defineAction({
297
84
  description:
298
- "Export a Content document as PDF-ready HTML, Markdown, or standalone HTML. PDF exports are print-ready HTML intended for the browser print dialog.",
85
+ "Export a Content page or bounded Database/View collection as CSV, a Markdown package, standalone HTML, or PDF-ready HTML for the browser print dialog.",
299
86
  schema: z.object({
300
87
  id: z.string().describe("Document ID (required)"),
301
88
  format: z
@@ -304,7 +91,9 @@ export default defineAction({
304
91
  .describe("Export format: pdf, markdown, html, or csv."),
305
92
  collection: collectionSchema
306
93
  .optional()
307
- .describe("Database CSV export scope and selected property IDs."),
94
+ .describe(
95
+ "Database export scope, selected scalar property IDs, primary body choice, and selected additional Blocks property IDs.",
96
+ ),
308
97
  title: z
309
98
  .string()
310
99
  .max(500)
@@ -323,30 +112,66 @@ export default defineAction({
323
112
  if (!access) throw new Error(`Document "${id}" not found`);
324
113
 
325
114
  const doc = access.resource;
326
- if (format === "csv") {
327
- if (!collection)
328
- throw new Error("CSV export requires collection options");
329
- const content = await databaseCsvContent(doc.id, collection);
115
+ const database = await getDatabaseByDocumentId(doc.id);
116
+ const effectiveCollection: CollectionExport | null = collection
117
+ ? collection
118
+ : database && format !== "csv"
119
+ ? {
120
+ scope: { kind: "all_members" },
121
+ propertyIds: [],
122
+ includePrimaryBody: true,
123
+ blockPropertyIds: [],
124
+ }
125
+ : null;
126
+ if (format === "csv" && !effectiveCollection) {
127
+ throw new Error("CSV export requires collection options");
128
+ }
129
+ if (effectiveCollection) {
130
+ const projection = await buildCollectionExportProjection(
131
+ doc.id,
132
+ effectiveCollection,
133
+ );
134
+ const baseName = exportBaseName(doc.title);
135
+ const deepLink = buildDeepLink({
136
+ app: "content",
137
+ view: "editor",
138
+ params: { documentId: doc.id },
139
+ });
140
+ if (format === "csv") {
141
+ const content = renderCollectionCsv(projection);
142
+ return {
143
+ id: doc.id,
144
+ title: doc.title || "Untitled",
145
+ format,
146
+ filename: `${baseName}.csv`,
147
+ mimeType: "text/csv;charset=utf-8",
148
+ content,
149
+ print: false,
150
+ deepLink,
151
+ };
152
+ }
153
+ if (format === "markdown") {
154
+ const archiveFiles = renderCollectionMarkdownArchive(projection);
155
+ return {
156
+ id: doc.id,
157
+ title: doc.title || "Untitled",
158
+ format,
159
+ filename: `${baseName}.zip`,
160
+ mimeType: "application/zip",
161
+ content: archiveFiles[0]?.content ?? "",
162
+ archiveFiles,
163
+ print: false,
164
+ deepLink,
165
+ };
166
+ }
330
167
  return {
331
- id: doc.id,
332
- title: doc.title || "Untitled",
333
- format,
334
- filename: `${
335
- (doc.title || "untitled")
336
- .replace(/[^a-z0-9]+/gi, "-")
337
- .replace(/^-+|-+$/g, "")
338
- .toLowerCase() || "untitled"
339
- }.csv`,
340
- mimeType: "text/csv;charset=utf-8",
341
- content,
342
- print: false,
343
- deepLink: buildDeepLink({
344
- app: "content",
345
- view: "editor",
346
- params: { documentId: doc.id },
347
- }),
168
+ ...renderCollectionHtml(projection, format),
169
+ deepLink,
348
170
  };
349
171
  }
172
+ if (format === "csv") {
173
+ throw new Error("CSV export requires collection options");
174
+ }
350
175
  const properties = await listPropertiesForAllDocumentDatabases(doc);
351
176
  const blocksFields = properties
352
177
  .filter((property) => isBlocksPropertyType(property.definition.type))
@@ -381,11 +206,10 @@ export default defineAction({
381
206
  identity,
382
207
  };
383
208
  });
384
- const collectionContent = await databaseExportContent(doc.id);
385
209
  const payload = buildDocumentExport({
386
210
  id: doc.id,
387
211
  title: title ?? doc.title,
388
- content: collectionContent ?? content ?? doc.content,
212
+ content: content ?? doc.content,
389
213
  updatedAt: doc.updatedAt,
390
214
  format,
391
215
  blocksFields,
@@ -1115,48 +1115,50 @@ export function DocumentToolbar({
1115
1115
  </DropdownMenuItem>
1116
1116
  </DropdownMenuGroup>
1117
1117
  <DropdownMenuSeparator />
1118
- <DropdownMenuSub>
1119
- <DropdownMenuSubTrigger disabled={exportDocument.isPending}>
1120
- {exportDocument.isPending ? (
1121
- <IconLoader2 className="me-2 h-4 w-4 animate-spin" />
1122
- ) : (
1123
- <IconDownload className="me-2 h-4 w-4" />
1124
- )}
1118
+ {databaseExportContext ? (
1119
+ <DropdownMenuItem
1120
+ onSelect={() => setDatabaseExportOpen(true)}
1121
+ >
1122
+ <IconDownload className="me-2 h-4 w-4" />
1125
1123
  {t("editor.toolbar.export")}
1126
- </DropdownMenuSubTrigger>
1127
- <DropdownMenuSubContent className="w-44">
1128
- {databaseExportContext ? (
1124
+ </DropdownMenuItem>
1125
+ ) : (
1126
+ <DropdownMenuSub>
1127
+ <DropdownMenuSubTrigger
1128
+ disabled={exportDocument.isPending}
1129
+ >
1130
+ {exportDocument.isPending ? (
1131
+ <IconLoader2 className="me-2 h-4 w-4 animate-spin" />
1132
+ ) : (
1133
+ <IconDownload className="me-2 h-4 w-4" />
1134
+ )}
1135
+ {t("editor.toolbar.export")}
1136
+ </DropdownMenuSubTrigger>
1137
+ <DropdownMenuSubContent className="w-44">
1129
1138
  <DropdownMenuItem
1130
1139
  disabled={exportDocument.isPending}
1131
- onSelect={() => setDatabaseExportOpen(true)}
1140
+ onSelect={() => void handleExport("pdf")}
1132
1141
  >
1133
- <IconDownload className="me-2 h-4 w-4" />
1134
- CSV
1142
+ <IconFileTypePdf className="me-2 h-4 w-4" />
1143
+ PDF
1135
1144
  </DropdownMenuItem>
1136
- ) : null}
1137
- <DropdownMenuItem
1138
- disabled={exportDocument.isPending}
1139
- onSelect={() => void handleExport("pdf")}
1140
- >
1141
- <IconFileTypePdf className="me-2 h-4 w-4" />
1142
- PDF
1143
- </DropdownMenuItem>
1144
- <DropdownMenuItem
1145
- disabled={exportDocument.isPending}
1146
- onSelect={() => void handleExport("markdown")}
1147
- >
1148
- <IconMarkdown className="me-2 h-4 w-4" />
1149
- Markdown
1150
- </DropdownMenuItem>
1151
- <DropdownMenuItem
1152
- disabled={exportDocument.isPending}
1153
- onSelect={() => void handleExport("html")}
1154
- >
1155
- <IconFileTypeHtml className="me-2 h-4 w-4" />
1156
- HTML
1157
- </DropdownMenuItem>
1158
- </DropdownMenuSubContent>
1159
- </DropdownMenuSub>
1145
+ <DropdownMenuItem
1146
+ disabled={exportDocument.isPending}
1147
+ onSelect={() => void handleExport("markdown")}
1148
+ >
1149
+ <IconMarkdown className="me-2 h-4 w-4" />
1150
+ Markdown
1151
+ </DropdownMenuItem>
1152
+ <DropdownMenuItem
1153
+ disabled={exportDocument.isPending}
1154
+ onSelect={() => void handleExport("html")}
1155
+ >
1156
+ <IconFileTypeHtml className="me-2 h-4 w-4" />
1157
+ HTML
1158
+ </DropdownMenuItem>
1159
+ </DropdownMenuSubContent>
1160
+ </DropdownMenuSub>
1161
+ )}
1160
1162
  </>
1161
1163
  )}
1162
1164
  <DropdownMenuSeparator />
@@ -1561,7 +1563,6 @@ export function DocumentToolbar({
1561
1563
  <DatabaseExportDialog
1562
1564
  documentId={documentId}
1563
1565
  context={databaseExportContext ?? null}
1564
- defaultScope="current_view"
1565
1566
  open={databaseExportOpen}
1566
1567
  onOpenChange={setDatabaseExportOpen}
1567
1568
  />