@agent-native/core 0.84.0 → 0.84.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 +1 -1
- package/corpus/core/CHANGELOG.md +6 -0
- package/corpus/core/docs/content/evals.mdx +34 -12
- package/corpus/core/docs/content/locales/ar-SA/evals.mdx +7 -0
- package/corpus/core/docs/content/locales/de-DE/evals.mdx +8 -0
- package/corpus/core/docs/content/locales/es-ES/evals.mdx +8 -0
- package/corpus/core/docs/content/locales/fr-FR/evals.mdx +8 -0
- package/corpus/core/docs/content/locales/hi-IN/evals.mdx +8 -0
- package/corpus/core/docs/content/locales/ja-JP/evals.mdx +7 -0
- package/corpus/core/docs/content/locales/ko-KR/evals.mdx +7 -0
- package/corpus/core/docs/content/locales/pt-BR/evals.mdx +8 -0
- package/corpus/core/docs/content/locales/zh-CN/evals.mdx +7 -0
- package/corpus/core/docs/content/locales/zh-TW/evals.mdx +7 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/eval/define-eval.ts +4 -1
- package/corpus/core/src/eval/report.ts +23 -8
- package/corpus/core/src/eval/runner.ts +41 -6
- package/corpus/core/src/eval/types.ts +11 -0
- package/corpus/templates/content/AGENTS.md +9 -2
- package/corpus/templates/content/actions/_database-row-batch.ts +230 -0
- package/corpus/templates/content/actions/_database-utils.ts +17 -9
- package/corpus/templates/content/actions/_notion-action-utils.ts +26 -0
- package/corpus/templates/content/actions/connect-notion-status.ts +13 -4
- package/corpus/templates/content/actions/create-and-link-notion-page.ts +23 -0
- package/corpus/templates/content/actions/delete-database-items.ts +55 -0
- package/corpus/templates/content/actions/delete-document.ts +4 -4
- package/corpus/templates/content/actions/disconnect-notion.ts +16 -0
- package/corpus/templates/content/actions/duplicate-database-item.ts +1 -1
- package/corpus/templates/content/actions/duplicate-database-items.ts +191 -0
- package/corpus/templates/content/actions/link-notion-page.ts +10 -7
- package/corpus/templates/content/actions/list-notion-links.ts +3 -4
- package/corpus/templates/content/actions/pull-notion-page.ts +7 -9
- package/corpus/templates/content/actions/push-notion-page.ts +7 -9
- package/corpus/templates/content/actions/refresh-notion-sync-status.ts +24 -0
- package/corpus/templates/content/actions/resolve-notion-sync-conflict.ts +23 -0
- package/corpus/templates/content/actions/search-notion-pages.ts +67 -0
- package/corpus/templates/content/actions/unlink-notion-page.ts +23 -0
- package/corpus/templates/content/app/components/editor/DocumentDatabase.tsx +27 -34
- package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +16 -13
- package/corpus/templates/content/app/components/editor/DocumentToolbar.tsx +10 -10
- package/corpus/templates/content/app/components/editor/NotionConflictBanner.tsx +1 -1
- package/corpus/templates/content/app/components/editor/NotionSyncBar.tsx +18 -9
- package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +27 -34
- package/corpus/templates/content/app/components/editor/database/navigation-state.ts +10 -4
- package/corpus/templates/content/app/components/sidebar/NotionButton.tsx +32 -17
- package/corpus/templates/content/app/hooks/use-content-database.ts +35 -0
- package/corpus/templates/content/app/hooks/use-notion.ts +108 -99
- package/corpus/templates/content/package.json +3 -0
- package/corpus/templates/content/parity/README.md +69 -0
- package/corpus/templates/content/parity/eval-scenarios.ts +118 -0
- package/corpus/templates/content/parity/exceptions.allowlist.ts +17 -0
- package/corpus/templates/content/parity/matrix.md +28 -0
- package/corpus/templates/content/parity/matrix.ts +606 -0
- package/corpus/templates/content/parity/matrix.types.ts +42 -0
- package/corpus/templates/content/parity/parity-evals.eval.ts +4 -0
- package/corpus/templates/content/parity/render-matrix.ts +90 -0
- package/corpus/templates/content/parity/scenario-to-eval.ts +57 -0
- package/corpus/templates/content/shared/api.ts +11 -0
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/awareness.d.ts.map +1 -1
- package/dist/collab/routes.d.ts +1 -1
- package/dist/eval/define-eval.d.ts.map +1 -1
- package/dist/eval/define-eval.js +2 -1
- package/dist/eval/define-eval.js.map +1 -1
- package/dist/eval/report.d.ts.map +1 -1
- package/dist/eval/report.js +19 -4
- package/dist/eval/report.js.map +1 -1
- package/dist/eval/runner.d.ts.map +1 -1
- package/dist/eval/runner.js +38 -6
- package/dist/eval/runner.js.map +1 -1
- package/dist/eval/types.d.ts +11 -0
- package/dist/eval/types.d.ts.map +1 -1
- package/dist/eval/types.js.map +1 -1
- package/dist/notifications/routes.d.ts +3 -3
- package/dist/progress/routes.d.ts +1 -1
- package/dist/resources/handlers.d.ts +1 -1
- package/docs/content/evals.mdx +34 -12
- package/docs/content/locales/ar-SA/evals.mdx +7 -0
- package/docs/content/locales/de-DE/evals.mdx +8 -0
- package/docs/content/locales/es-ES/evals.mdx +8 -0
- package/docs/content/locales/fr-FR/evals.mdx +8 -0
- package/docs/content/locales/hi-IN/evals.mdx +8 -0
- package/docs/content/locales/ja-JP/evals.mdx +7 -0
- package/docs/content/locales/ko-KR/evals.mdx +7 -0
- package/docs/content/locales/pt-BR/evals.mdx +8 -0
- package/docs/content/locales/zh-CN/evals.mdx +7 -0
- package/docs/content/locales/zh-TW/evals.mdx +7 -0
- package/package.json +1 -1
- package/corpus/templates/content/server/routes/api/notion/disconnect.post.ts +0 -12
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
import { and, asc, eq, inArray, isNull, or, sql } from "drizzle-orm";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
|
|
4
|
+
import { getDb, schema } from "../server/db/index.js";
|
|
5
|
+
|
|
6
|
+
export const DATABASE_ROW_BATCH_LIMIT = 100;
|
|
7
|
+
|
|
8
|
+
export const databaseRowBatchSchema = z
|
|
9
|
+
.object({
|
|
10
|
+
databaseId: z.string().optional().describe("Content database ID"),
|
|
11
|
+
documentId: z
|
|
12
|
+
.string()
|
|
13
|
+
.optional()
|
|
14
|
+
.describe("Content database backing document ID"),
|
|
15
|
+
itemIds: z
|
|
16
|
+
.array(z.string())
|
|
17
|
+
.max(DATABASE_ROW_BATCH_LIMIT)
|
|
18
|
+
.optional()
|
|
19
|
+
.describe(
|
|
20
|
+
'Native JSON array of database row item IDs to mutate in one batch, for example ["item_1", "item_2"].',
|
|
21
|
+
),
|
|
22
|
+
documentIds: z
|
|
23
|
+
.array(z.string())
|
|
24
|
+
.max(DATABASE_ROW_BATCH_LIMIT)
|
|
25
|
+
.optional()
|
|
26
|
+
.describe(
|
|
27
|
+
'Native JSON array of database row document IDs to mutate in one batch, for example ["doc_1", "doc_2"].',
|
|
28
|
+
),
|
|
29
|
+
})
|
|
30
|
+
.superRefine((value, ctx) => {
|
|
31
|
+
if (!value.databaseId && !value.documentId) {
|
|
32
|
+
ctx.addIssue({
|
|
33
|
+
code: z.ZodIssueCode.custom,
|
|
34
|
+
message: "Either databaseId or documentId is required.",
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
const total =
|
|
38
|
+
(value.itemIds?.length ?? 0) + (value.documentIds?.length ?? 0);
|
|
39
|
+
if (total === 0) {
|
|
40
|
+
ctx.addIssue({
|
|
41
|
+
code: z.ZodIssueCode.custom,
|
|
42
|
+
message: "At least one itemId or documentId is required.",
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
if (total > DATABASE_ROW_BATCH_LIMIT) {
|
|
46
|
+
ctx.addIssue({
|
|
47
|
+
code: z.ZodIssueCode.custom,
|
|
48
|
+
message: `Database row batch is limited to ${DATABASE_ROW_BATCH_LIMIT} rows.`,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
export type DatabaseRowBatchInput = z.infer<typeof databaseRowBatchSchema>;
|
|
54
|
+
|
|
55
|
+
export type DatabaseRowBatchRow = {
|
|
56
|
+
item: typeof schema.contentDatabaseItems.$inferSelect;
|
|
57
|
+
database: typeof schema.contentDatabases.$inferSelect;
|
|
58
|
+
document: typeof schema.documents.$inferSelect;
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
function findDuplicates(values: string[]) {
|
|
62
|
+
const seen = new Set<string>();
|
|
63
|
+
const duplicates = new Set<string>();
|
|
64
|
+
for (const value of values) {
|
|
65
|
+
if (seen.has(value)) duplicates.add(value);
|
|
66
|
+
seen.add(value);
|
|
67
|
+
}
|
|
68
|
+
return [...duplicates];
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function positionCaseSql(
|
|
72
|
+
idColumn: unknown,
|
|
73
|
+
fallbackColumn: unknown,
|
|
74
|
+
orderedIds: string[],
|
|
75
|
+
) {
|
|
76
|
+
const cases = orderedIds.map((id, index) => sql`WHEN ${id} THEN ${index}`);
|
|
77
|
+
return sql<number>`CASE ${idColumn} ${sql.join(cases, sql` `)} ELSE ${fallbackColumn} END`;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export async function resolveDatabaseRowsForBatch(
|
|
81
|
+
input: DatabaseRowBatchInput,
|
|
82
|
+
): Promise<{
|
|
83
|
+
database: typeof schema.contentDatabases.$inferSelect;
|
|
84
|
+
rows: DatabaseRowBatchRow[];
|
|
85
|
+
}> {
|
|
86
|
+
const itemIds = input.itemIds ?? [];
|
|
87
|
+
const documentIds = input.documentIds ?? [];
|
|
88
|
+
const requestedCount = itemIds.length + documentIds.length;
|
|
89
|
+
if (!input.databaseId && !input.documentId) {
|
|
90
|
+
throw new Error("Either databaseId or documentId is required.");
|
|
91
|
+
}
|
|
92
|
+
if (requestedCount === 0) {
|
|
93
|
+
throw new Error("At least one itemId or documentId is required.");
|
|
94
|
+
}
|
|
95
|
+
if (requestedCount > DATABASE_ROW_BATCH_LIMIT) {
|
|
96
|
+
throw new Error(
|
|
97
|
+
`Database row batch is limited to ${DATABASE_ROW_BATCH_LIMIT} rows.`,
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
const duplicateItemIds = findDuplicates(itemIds);
|
|
101
|
+
const duplicateDocumentIds = findDuplicates(documentIds);
|
|
102
|
+
if (duplicateItemIds.length > 0 || duplicateDocumentIds.length > 0) {
|
|
103
|
+
throw new Error("Duplicate database row IDs are not allowed in one batch.");
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const db = getDb();
|
|
107
|
+
const [database] = await db
|
|
108
|
+
.select()
|
|
109
|
+
.from(schema.contentDatabases)
|
|
110
|
+
.where(
|
|
111
|
+
and(
|
|
112
|
+
input.databaseId
|
|
113
|
+
? eq(schema.contentDatabases.id, input.databaseId)
|
|
114
|
+
: undefined,
|
|
115
|
+
input.documentId
|
|
116
|
+
? eq(schema.contentDatabases.documentId, input.documentId)
|
|
117
|
+
: undefined,
|
|
118
|
+
isNull(schema.contentDatabases.deletedAt),
|
|
119
|
+
),
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
if (!database) throw new Error("Database not found.");
|
|
123
|
+
|
|
124
|
+
const rowPredicates = [];
|
|
125
|
+
if (itemIds.length > 0) {
|
|
126
|
+
rowPredicates.push(inArray(schema.contentDatabaseItems.id, itemIds));
|
|
127
|
+
}
|
|
128
|
+
if (documentIds.length > 0) {
|
|
129
|
+
rowPredicates.push(
|
|
130
|
+
inArray(schema.contentDatabaseItems.documentId, documentIds),
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const requestedRows = await db
|
|
135
|
+
.select({
|
|
136
|
+
item: schema.contentDatabaseItems,
|
|
137
|
+
database: schema.contentDatabases,
|
|
138
|
+
document: schema.documents,
|
|
139
|
+
})
|
|
140
|
+
.from(schema.contentDatabaseItems)
|
|
141
|
+
.innerJoin(
|
|
142
|
+
schema.contentDatabases,
|
|
143
|
+
eq(schema.contentDatabases.id, schema.contentDatabaseItems.databaseId),
|
|
144
|
+
)
|
|
145
|
+
.innerJoin(
|
|
146
|
+
schema.documents,
|
|
147
|
+
eq(schema.documents.id, schema.contentDatabaseItems.documentId),
|
|
148
|
+
)
|
|
149
|
+
.where(
|
|
150
|
+
and(
|
|
151
|
+
eq(schema.contentDatabaseItems.databaseId, database.id),
|
|
152
|
+
rowPredicates.length === 1 ? rowPredicates[0] : or(...rowPredicates),
|
|
153
|
+
isNull(schema.contentDatabases.deletedAt),
|
|
154
|
+
),
|
|
155
|
+
)
|
|
156
|
+
.orderBy(asc(schema.contentDatabaseItems.position));
|
|
157
|
+
|
|
158
|
+
const itemIdMatches = new Set(requestedRows.map((row) => row.item.id));
|
|
159
|
+
const documentIdMatches = new Set(
|
|
160
|
+
requestedRows.map((row) => row.item.documentId),
|
|
161
|
+
);
|
|
162
|
+
const missingItemIds = itemIds.filter((id) => !itemIdMatches.has(id));
|
|
163
|
+
const missingDocumentIds = documentIds.filter(
|
|
164
|
+
(id) => !documentIdMatches.has(id),
|
|
165
|
+
);
|
|
166
|
+
if (missingItemIds.length > 0 || missingDocumentIds.length > 0) {
|
|
167
|
+
throw new Error("All requested rows must exist in the target database.");
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (requestedRows.length !== requestedCount) {
|
|
171
|
+
throw new Error("Duplicate database row references are not allowed.");
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const rowIds = requestedRows.map((row) => row.item.id);
|
|
175
|
+
if (new Set(rowIds).size !== rowIds.length) {
|
|
176
|
+
throw new Error("Duplicate database row references are not allowed.");
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return { database, rows: requestedRows };
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export async function renumberDatabaseRows(
|
|
183
|
+
db: ReturnType<typeof getDb>,
|
|
184
|
+
database: typeof schema.contentDatabases.$inferSelect,
|
|
185
|
+
now: string,
|
|
186
|
+
) {
|
|
187
|
+
const rows = await db
|
|
188
|
+
.select()
|
|
189
|
+
.from(schema.contentDatabaseItems)
|
|
190
|
+
.where(eq(schema.contentDatabaseItems.databaseId, database.id))
|
|
191
|
+
.orderBy(asc(schema.contentDatabaseItems.position));
|
|
192
|
+
if (rows.length === 0) return;
|
|
193
|
+
|
|
194
|
+
const itemIds = rows.map((row) => row.id);
|
|
195
|
+
const documentIds = rows.map((row) => row.documentId);
|
|
196
|
+
await db
|
|
197
|
+
.update(schema.contentDatabaseItems)
|
|
198
|
+
.set({
|
|
199
|
+
position: positionCaseSql(
|
|
200
|
+
schema.contentDatabaseItems.id,
|
|
201
|
+
schema.contentDatabaseItems.position,
|
|
202
|
+
itemIds,
|
|
203
|
+
),
|
|
204
|
+
updatedAt: now,
|
|
205
|
+
})
|
|
206
|
+
.where(
|
|
207
|
+
and(
|
|
208
|
+
eq(schema.contentDatabaseItems.databaseId, database.id),
|
|
209
|
+
inArray(schema.contentDatabaseItems.id, itemIds),
|
|
210
|
+
),
|
|
211
|
+
);
|
|
212
|
+
|
|
213
|
+
await db
|
|
214
|
+
.update(schema.documents)
|
|
215
|
+
.set({
|
|
216
|
+
position: positionCaseSql(
|
|
217
|
+
schema.documents.id,
|
|
218
|
+
schema.documents.position,
|
|
219
|
+
documentIds,
|
|
220
|
+
),
|
|
221
|
+
updatedAt: now,
|
|
222
|
+
})
|
|
223
|
+
.where(
|
|
224
|
+
and(
|
|
225
|
+
eq(schema.documents.ownerEmail, database.ownerEmail),
|
|
226
|
+
eq(schema.documents.parentId, database.documentId),
|
|
227
|
+
inArray(schema.documents.id, documentIds),
|
|
228
|
+
),
|
|
229
|
+
);
|
|
230
|
+
}
|
|
@@ -285,8 +285,8 @@ export async function isSoftDeletedDatabaseDocument(documentId: string) {
|
|
|
285
285
|
export async function getDatabaseByDocumentId(
|
|
286
286
|
documentId: string,
|
|
287
287
|
options: { includeDeleted?: boolean } = {},
|
|
288
|
+
db = getDb(),
|
|
288
289
|
) {
|
|
289
|
-
const db = getDb();
|
|
290
290
|
const clauses = [eq(schema.contentDatabases.documentId, documentId)];
|
|
291
291
|
if (!options.includeDeleted) {
|
|
292
292
|
clauses.push(isNull(schema.contentDatabases.deletedAt));
|
|
@@ -301,8 +301,8 @@ export async function getDatabaseByDocumentId(
|
|
|
301
301
|
export async function getDatabaseItemByDocumentId(
|
|
302
302
|
documentId: string,
|
|
303
303
|
options: { includeDeleted?: boolean } = {},
|
|
304
|
+
db = getDb(),
|
|
304
305
|
) {
|
|
305
|
-
const db = getDb();
|
|
306
306
|
const clauses = [eq(schema.contentDatabaseItems.documentId, documentId)];
|
|
307
307
|
if (!options.includeDeleted) {
|
|
308
308
|
clauses.push(isNull(schema.contentDatabases.deletedAt));
|
|
@@ -332,11 +332,15 @@ export async function getDatabaseItemByDocumentId(
|
|
|
332
332
|
export async function deleteDatabaseDataForDocument(
|
|
333
333
|
documentId: string,
|
|
334
334
|
ownerEmail: string,
|
|
335
|
+
db = getDb(),
|
|
335
336
|
) {
|
|
336
|
-
const
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
337
|
+
const database = await getDatabaseByDocumentId(
|
|
338
|
+
documentId,
|
|
339
|
+
{
|
|
340
|
+
includeDeleted: true,
|
|
341
|
+
},
|
|
342
|
+
db,
|
|
343
|
+
);
|
|
340
344
|
if (database) {
|
|
341
345
|
const definitions = await db
|
|
342
346
|
.select({ id: schema.documentPropertyDefinitions.id })
|
|
@@ -395,9 +399,13 @@ export async function deleteDatabaseDataForDocument(
|
|
|
395
399
|
.where(eq(schema.contentDatabases.id, database.id));
|
|
396
400
|
}
|
|
397
401
|
|
|
398
|
-
const item = await getDatabaseItemByDocumentId(
|
|
399
|
-
|
|
400
|
-
|
|
402
|
+
const item = await getDatabaseItemByDocumentId(
|
|
403
|
+
documentId,
|
|
404
|
+
{
|
|
405
|
+
includeDeleted: true,
|
|
406
|
+
},
|
|
407
|
+
db,
|
|
408
|
+
);
|
|
401
409
|
if (item) {
|
|
402
410
|
await db
|
|
403
411
|
.delete(schema.contentDatabaseBodyHydrationQueue)
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getRequestOrgId,
|
|
3
|
+
getRequestUserEmail,
|
|
4
|
+
} from "@agent-native/core/server";
|
|
5
|
+
import { assertAccess } from "@agent-native/core/sharing";
|
|
6
|
+
|
|
7
|
+
export function getCurrentNotionOwner() {
|
|
8
|
+
const owner = getRequestUserEmail();
|
|
9
|
+
if (!owner) throw new Error("no authenticated user");
|
|
10
|
+
return owner;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export async function getNotionDocumentOwner(documentId: string) {
|
|
14
|
+
const userEmail = getCurrentNotionOwner();
|
|
15
|
+
await assertAccess("document", documentId, "editor", {
|
|
16
|
+
userEmail,
|
|
17
|
+
orgId: getRequestOrgId(),
|
|
18
|
+
});
|
|
19
|
+
return userEmail;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function resolveDocumentId(args: { documentId?: string; id?: string }) {
|
|
23
|
+
const documentId = args.documentId || args.id;
|
|
24
|
+
if (!documentId) throw new Error("documentId is required");
|
|
25
|
+
return documentId;
|
|
26
|
+
}
|
|
@@ -1,21 +1,30 @@
|
|
|
1
1
|
import { defineAction } from "@agent-native/core";
|
|
2
|
-
import {
|
|
2
|
+
import { resolveSecret } from "@agent-native/core/server";
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
|
|
5
5
|
import { getNotionConnectionForOwner } from "../server/lib/notion.js";
|
|
6
|
+
import { getCurrentNotionOwner } from "./_notion-action-utils.js";
|
|
6
7
|
|
|
7
8
|
export default defineAction({
|
|
8
9
|
description: "Check Notion connection status for the current user.",
|
|
9
10
|
schema: z.object({}),
|
|
10
|
-
http:
|
|
11
|
+
http: { method: "GET" },
|
|
11
12
|
run: async () => {
|
|
12
|
-
const owner =
|
|
13
|
-
if (!owner) throw new Error("no authenticated user");
|
|
13
|
+
const owner = getCurrentNotionOwner();
|
|
14
14
|
const connection = await getNotionConnectionForOwner(owner);
|
|
15
|
+
const hasOAuthCredentials = Boolean(
|
|
16
|
+
(await resolveSecret("NOTION_CLIENT_ID")) &&
|
|
17
|
+
(await resolveSecret("NOTION_CLIENT_SECRET")),
|
|
18
|
+
);
|
|
19
|
+
|
|
15
20
|
return {
|
|
16
21
|
connected: Boolean(connection),
|
|
17
22
|
workspaceName: connection?.workspaceName ?? null,
|
|
18
23
|
workspaceId: connection?.workspaceId ?? null,
|
|
24
|
+
authUrl: null,
|
|
25
|
+
error:
|
|
26
|
+
connection || hasOAuthCredentials ? undefined : "missing_credentials",
|
|
27
|
+
mode: connection ? ("oauth" as const) : null,
|
|
19
28
|
};
|
|
20
29
|
},
|
|
21
30
|
});
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { defineAction } from "@agent-native/core";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
|
|
4
|
+
import { createAndLinkNotionPage } from "../server/lib/notion-sync.js";
|
|
5
|
+
import {
|
|
6
|
+
getNotionDocumentOwner,
|
|
7
|
+
resolveDocumentId,
|
|
8
|
+
} from "./_notion-action-utils.js";
|
|
9
|
+
|
|
10
|
+
export default defineAction({
|
|
11
|
+
description: "Create a Notion page from a Content document and link it.",
|
|
12
|
+
schema: z.object({
|
|
13
|
+
documentId: z.string().optional().describe("Document ID (required)"),
|
|
14
|
+
id: z.string().optional().describe("Alias for --documentId"),
|
|
15
|
+
parentPageIdOrUrl: z.string().optional(),
|
|
16
|
+
}),
|
|
17
|
+
http: { method: "POST" },
|
|
18
|
+
run: async (args) => {
|
|
19
|
+
const documentId = resolveDocumentId(args);
|
|
20
|
+
const owner = await getNotionDocumentOwner(documentId);
|
|
21
|
+
return createAndLinkNotionPage(owner, documentId, args.parentPageIdOrUrl);
|
|
22
|
+
},
|
|
23
|
+
});
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { defineAction } from "@agent-native/core";
|
|
2
|
+
import { writeAppState } from "@agent-native/core/application-state";
|
|
3
|
+
import { assertAccess } from "@agent-native/core/sharing";
|
|
4
|
+
|
|
5
|
+
import { getDb } from "../server/db/index.js";
|
|
6
|
+
import {
|
|
7
|
+
databaseRowBatchSchema,
|
|
8
|
+
renumberDatabaseRows,
|
|
9
|
+
resolveDatabaseRowsForBatch,
|
|
10
|
+
} from "./_database-row-batch.js";
|
|
11
|
+
import { getContentDatabaseResponse } from "./_database-utils.js";
|
|
12
|
+
import { deleteDocumentRecursive } from "./delete-document.js";
|
|
13
|
+
|
|
14
|
+
export default defineAction({
|
|
15
|
+
description:
|
|
16
|
+
"Delete multiple page rows from a content database in one atomic batch. Use this for two or more selected/named rows instead of looping delete-document.",
|
|
17
|
+
schema: databaseRowBatchSchema,
|
|
18
|
+
run: async (args) => {
|
|
19
|
+
const db = getDb();
|
|
20
|
+
const { database, rows } = await resolveDatabaseRowsForBatch(args);
|
|
21
|
+
|
|
22
|
+
await assertAccess("document", database.documentId, "editor");
|
|
23
|
+
for (const row of rows) {
|
|
24
|
+
await assertAccess("document", row.document.id, "admin");
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const deletedItemIds = rows.map((row) => row.item.id);
|
|
28
|
+
const deletedDocumentIds = rows.map((row) => row.document.id);
|
|
29
|
+
const now = new Date().toISOString();
|
|
30
|
+
|
|
31
|
+
await db.transaction(async (tx) => {
|
|
32
|
+
for (const row of rows) {
|
|
33
|
+
await deleteDocumentRecursive(
|
|
34
|
+
tx as unknown as ReturnType<typeof getDb>,
|
|
35
|
+
row.document.id,
|
|
36
|
+
row.document.ownerEmail,
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
await renumberDatabaseRows(
|
|
40
|
+
tx as unknown as ReturnType<typeof getDb>,
|
|
41
|
+
database,
|
|
42
|
+
now,
|
|
43
|
+
);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
await writeAppState("refresh-signal", { ts: Date.now() });
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
...(await getContentDatabaseResponse(database.id)),
|
|
50
|
+
deletedItemIds,
|
|
51
|
+
deletedDocumentIds,
|
|
52
|
+
deletedCount: deletedItemIds.length,
|
|
53
|
+
};
|
|
54
|
+
},
|
|
55
|
+
});
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
isContentLocalFileMode,
|
|
13
13
|
} from "./_local-file-documents.js";
|
|
14
14
|
|
|
15
|
-
async function
|
|
15
|
+
export async function deleteDocumentRecursive(
|
|
16
16
|
db: ReturnType<typeof getDb>,
|
|
17
17
|
id: string,
|
|
18
18
|
ownerEmail: string,
|
|
@@ -29,11 +29,11 @@ async function deleteRecursive(
|
|
|
29
29
|
|
|
30
30
|
const deleted: string[] = [];
|
|
31
31
|
for (const child of children) {
|
|
32
|
-
deleted.push(...(await
|
|
32
|
+
deleted.push(...(await deleteDocumentRecursive(db, child.id, ownerEmail)));
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
// Delete database membership/schema, sync links, versions, shares, then document.
|
|
36
|
-
await deleteDatabaseDataForDocument(id, ownerEmail);
|
|
36
|
+
await deleteDatabaseDataForDocument(id, ownerEmail, db);
|
|
37
37
|
await db
|
|
38
38
|
.delete(schema.documentSyncLinks)
|
|
39
39
|
.where(
|
|
@@ -86,7 +86,7 @@ export default defineAction({
|
|
|
86
86
|
const existing = access.resource;
|
|
87
87
|
|
|
88
88
|
const db = getDb();
|
|
89
|
-
const deleted = await
|
|
89
|
+
const deleted = await deleteDocumentRecursive(
|
|
90
90
|
db,
|
|
91
91
|
id,
|
|
92
92
|
existing.ownerEmail as string,
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { defineAction } from "@agent-native/core";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
|
|
4
|
+
import { disconnectNotionForOwner } from "../server/lib/notion.js";
|
|
5
|
+
import { getCurrentNotionOwner } from "./_notion-action-utils.js";
|
|
6
|
+
|
|
7
|
+
export default defineAction({
|
|
8
|
+
description: "Disconnect the current user's Notion workspace.",
|
|
9
|
+
schema: z.object({}),
|
|
10
|
+
http: { method: "POST" },
|
|
11
|
+
run: async () => {
|
|
12
|
+
const owner = getCurrentNotionOwner();
|
|
13
|
+
const deleted = await disconnectNotionForOwner(owner);
|
|
14
|
+
return { success: true, deleted };
|
|
15
|
+
},
|
|
16
|
+
});
|
|
@@ -11,7 +11,7 @@ import { nanoid } from "./_property-utils.js";
|
|
|
11
11
|
|
|
12
12
|
export default defineAction({
|
|
13
13
|
description:
|
|
14
|
-
"Duplicate
|
|
14
|
+
"Duplicate exactly one page row in a content database, including stored property values. For two or more rows, use duplicate-database-items once instead of looping this action.",
|
|
15
15
|
schema: z.object({
|
|
16
16
|
itemId: z.string().optional().describe("Database item ID"),
|
|
17
17
|
documentId: z.string().optional().describe("Database row document ID"),
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import { defineAction } from "@agent-native/core";
|
|
2
|
+
import { writeAppState } from "@agent-native/core/application-state";
|
|
3
|
+
import { getRequestUserEmail } from "@agent-native/core/server/request-context";
|
|
4
|
+
import { assertAccess } from "@agent-native/core/sharing";
|
|
5
|
+
import { and, eq, gte, inArray, sql } from "drizzle-orm";
|
|
6
|
+
|
|
7
|
+
import { getDb, schema } from "../server/db/index.js";
|
|
8
|
+
import {
|
|
9
|
+
databaseRowBatchSchema,
|
|
10
|
+
resolveDatabaseRowsForBatch,
|
|
11
|
+
} from "./_database-row-batch.js";
|
|
12
|
+
import { getContentDatabaseResponse } from "./_database-utils.js";
|
|
13
|
+
import { nanoid } from "./_property-utils.js";
|
|
14
|
+
|
|
15
|
+
export default defineAction({
|
|
16
|
+
description:
|
|
17
|
+
"Duplicate multiple page rows in a content database in one atomic batch. Use this for two or more selected/named rows instead of looping duplicate-database-item.",
|
|
18
|
+
schema: databaseRowBatchSchema,
|
|
19
|
+
run: async (args) => {
|
|
20
|
+
const db = getDb();
|
|
21
|
+
const { database, rows } = await resolveDatabaseRowsForBatch(args);
|
|
22
|
+
|
|
23
|
+
await assertAccess("document", database.documentId, "editor");
|
|
24
|
+
for (const row of rows) {
|
|
25
|
+
await assertAccess("document", row.document.id, "viewer");
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const sourceDocumentIds = rows.map((row) => row.document.id);
|
|
29
|
+
const sourceItemIds = rows.map((row) => row.item.id);
|
|
30
|
+
const now = new Date().toISOString();
|
|
31
|
+
const insertionPosition =
|
|
32
|
+
Math.max(...rows.map((row) => row.item.position)) + 1;
|
|
33
|
+
const currentUserEmail = getRequestUserEmail() ?? database.ownerEmail;
|
|
34
|
+
|
|
35
|
+
const values =
|
|
36
|
+
sourceDocumentIds.length > 0
|
|
37
|
+
? await db
|
|
38
|
+
.select()
|
|
39
|
+
.from(schema.documentPropertyValues)
|
|
40
|
+
.where(
|
|
41
|
+
inArray(
|
|
42
|
+
schema.documentPropertyValues.documentId,
|
|
43
|
+
sourceDocumentIds,
|
|
44
|
+
),
|
|
45
|
+
)
|
|
46
|
+
: [];
|
|
47
|
+
const valuesByDocumentId = new Map<
|
|
48
|
+
string,
|
|
49
|
+
Array<typeof schema.documentPropertyValues.$inferSelect>
|
|
50
|
+
>();
|
|
51
|
+
for (const value of values) {
|
|
52
|
+
const list = valuesByDocumentId.get(value.documentId) ?? [];
|
|
53
|
+
list.push(value);
|
|
54
|
+
valuesByDocumentId.set(value.documentId, list);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const inheritedShares = await db
|
|
58
|
+
.select({
|
|
59
|
+
principalType: schema.documentShares.principalType,
|
|
60
|
+
principalId: schema.documentShares.principalId,
|
|
61
|
+
role: schema.documentShares.role,
|
|
62
|
+
})
|
|
63
|
+
.from(schema.documentShares)
|
|
64
|
+
.where(eq(schema.documentShares.resourceId, database.documentId));
|
|
65
|
+
|
|
66
|
+
const duplicates = rows.map((row, index) => ({
|
|
67
|
+
sourceItemId: row.item.id,
|
|
68
|
+
sourceDocumentId: row.document.id,
|
|
69
|
+
duplicatedItemId: nanoid(),
|
|
70
|
+
duplicatedDocumentId: nanoid(),
|
|
71
|
+
position: insertionPosition + index,
|
|
72
|
+
row,
|
|
73
|
+
}));
|
|
74
|
+
|
|
75
|
+
await db.transaction(async (tx) => {
|
|
76
|
+
await tx
|
|
77
|
+
.update(schema.contentDatabaseItems)
|
|
78
|
+
.set({
|
|
79
|
+
position: sql`${schema.contentDatabaseItems.position} + ${duplicates.length}`,
|
|
80
|
+
updatedAt: now,
|
|
81
|
+
})
|
|
82
|
+
.where(
|
|
83
|
+
and(
|
|
84
|
+
eq(schema.contentDatabaseItems.databaseId, database.id),
|
|
85
|
+
gte(schema.contentDatabaseItems.position, insertionPosition),
|
|
86
|
+
),
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
await tx
|
|
90
|
+
.update(schema.documents)
|
|
91
|
+
.set({
|
|
92
|
+
position: sql`${schema.documents.position} + ${duplicates.length}`,
|
|
93
|
+
updatedAt: now,
|
|
94
|
+
})
|
|
95
|
+
.where(
|
|
96
|
+
and(
|
|
97
|
+
eq(schema.documents.ownerEmail, database.ownerEmail),
|
|
98
|
+
eq(schema.documents.parentId, database.documentId),
|
|
99
|
+
gte(schema.documents.position, insertionPosition),
|
|
100
|
+
),
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
await tx.insert(schema.documents).values(
|
|
104
|
+
duplicates.map((duplicate) => ({
|
|
105
|
+
id: duplicate.duplicatedDocumentId,
|
|
106
|
+
ownerEmail: duplicate.row.document.ownerEmail,
|
|
107
|
+
orgId: duplicate.row.document.orgId,
|
|
108
|
+
parentId: database.documentId,
|
|
109
|
+
title: `Copy of ${duplicate.row.document.title.trim() || "Untitled"}`,
|
|
110
|
+
content: duplicate.row.document.content,
|
|
111
|
+
icon: duplicate.row.document.icon,
|
|
112
|
+
position: duplicate.position,
|
|
113
|
+
isFavorite: 0,
|
|
114
|
+
hideFromSearch: duplicate.row.document.hideFromSearch,
|
|
115
|
+
visibility: duplicate.row.document.visibility,
|
|
116
|
+
createdAt: now,
|
|
117
|
+
updatedAt: now,
|
|
118
|
+
})),
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
await tx.insert(schema.contentDatabaseItems).values(
|
|
122
|
+
duplicates.map((duplicate) => ({
|
|
123
|
+
id: duplicate.duplicatedItemId,
|
|
124
|
+
ownerEmail: duplicate.row.item.ownerEmail,
|
|
125
|
+
orgId: duplicate.row.item.orgId,
|
|
126
|
+
databaseId: database.id,
|
|
127
|
+
documentId: duplicate.duplicatedDocumentId,
|
|
128
|
+
position: duplicate.position,
|
|
129
|
+
createdAt: now,
|
|
130
|
+
updatedAt: now,
|
|
131
|
+
})),
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
const duplicatedValues = duplicates.flatMap((duplicate) =>
|
|
135
|
+
(valuesByDocumentId.get(duplicate.sourceDocumentId) ?? []).map(
|
|
136
|
+
(value) => ({
|
|
137
|
+
id: nanoid(),
|
|
138
|
+
ownerEmail: duplicate.row.document.ownerEmail,
|
|
139
|
+
documentId: duplicate.duplicatedDocumentId,
|
|
140
|
+
propertyId: value.propertyId,
|
|
141
|
+
valueJson: value.valueJson,
|
|
142
|
+
createdAt: now,
|
|
143
|
+
updatedAt: now,
|
|
144
|
+
}),
|
|
145
|
+
),
|
|
146
|
+
);
|
|
147
|
+
if (duplicatedValues.length > 0) {
|
|
148
|
+
await tx.insert(schema.documentPropertyValues).values(duplicatedValues);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (inheritedShares.length > 0) {
|
|
152
|
+
await tx.insert(schema.documentShares).values(
|
|
153
|
+
duplicates.flatMap((duplicate) =>
|
|
154
|
+
inheritedShares.map((share) => ({
|
|
155
|
+
id: nanoid(),
|
|
156
|
+
resourceId: duplicate.duplicatedDocumentId,
|
|
157
|
+
principalType: share.principalType,
|
|
158
|
+
principalId: share.principalId,
|
|
159
|
+
role: share.role,
|
|
160
|
+
createdBy: currentUserEmail,
|
|
161
|
+
createdAt: now,
|
|
162
|
+
})),
|
|
163
|
+
),
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
await writeAppState("refresh-signal", { ts: Date.now() });
|
|
169
|
+
|
|
170
|
+
return {
|
|
171
|
+
...(await getContentDatabaseResponse(database.id)),
|
|
172
|
+
duplicatedItemId: duplicates[0]?.duplicatedItemId,
|
|
173
|
+
duplicatedDocumentId: duplicates[0]?.duplicatedDocumentId,
|
|
174
|
+
duplicatedItemIds: duplicates.map(
|
|
175
|
+
(duplicate) => duplicate.duplicatedItemId,
|
|
176
|
+
),
|
|
177
|
+
duplicatedDocumentIds: duplicates.map(
|
|
178
|
+
(duplicate) => duplicate.duplicatedDocumentId,
|
|
179
|
+
),
|
|
180
|
+
sourceItemIds,
|
|
181
|
+
sourceDocumentIds,
|
|
182
|
+
sourceToDuplicate: duplicates.map((duplicate) => ({
|
|
183
|
+
sourceItemId: duplicate.sourceItemId,
|
|
184
|
+
sourceDocumentId: duplicate.sourceDocumentId,
|
|
185
|
+
duplicatedItemId: duplicate.duplicatedItemId,
|
|
186
|
+
duplicatedDocumentId: duplicate.duplicatedDocumentId,
|
|
187
|
+
})),
|
|
188
|
+
duplicatedCount: duplicates.length,
|
|
189
|
+
};
|
|
190
|
+
},
|
|
191
|
+
});
|