@agent-native/core 0.154.3 → 0.154.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.
- package/corpus/README.md +1 -1
- package/corpus/templates/content/.agents/skills/content/SKILL.md +2 -2
- package/corpus/templates/content/.agents/skills/document-editing/references/databases.md +20 -4
- package/corpus/templates/content/actions/_blocks-field-identity.ts +464 -0
- package/corpus/templates/content/actions/_database-row-mutation.ts +1490 -0
- package/corpus/templates/content/actions/_database-utils.ts +55 -3
- package/corpus/templates/content/actions/_property-utils.ts +141 -31
- package/corpus/templates/content/actions/add-database-item.ts +62 -240
- package/corpus/templates/content/actions/configure-document-property.ts +170 -1
- package/corpus/templates/content/actions/delete-document-property.ts +56 -17
- package/corpus/templates/content/actions/delete-document.ts +3 -0
- package/corpus/templates/content/actions/edit-document.ts +18 -1
- package/corpus/templates/content/actions/export-document.ts +41 -0
- package/corpus/templates/content/actions/remove-database-items.ts +10 -0
- package/corpus/templates/content/actions/set-document-property.ts +140 -3
- package/corpus/templates/content/actions/update-database-item.ts +62 -0
- package/corpus/templates/content/actions/update-document.ts +24 -0
- package/corpus/templates/content/actions/upsert-database-item-by-key.ts +43 -796
- package/corpus/templates/content/app/components/editor/DocumentBlockFields.tsx +99 -11
- package/corpus/templates/content/app/components/editor/blockFieldSaveController.ts +6 -0
- package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +10 -8
- package/corpus/templates/content/app/hooks/use-content-database.ts +33 -32
- package/corpus/templates/content/app/hooks/use-document-properties.ts +4 -1
- package/corpus/templates/content/app/i18n-data.ts +20 -0
- package/corpus/templates/content/changelog/2026-08-10-database-blocks-fields-now-keep-logical-block-identity-throu.md +6 -0
- package/corpus/templates/content/changelog/2026-08-10-database-row-actions-now-validate-exact-schemas-and-safe-ret.md +6 -0
- package/corpus/templates/content/changelog/2026-08-13-blocks-field-conflicts-now-keep-the-newer-saved-version-visi.md +6 -0
- package/corpus/templates/content/docs/product/capabilities/content.object.block.md +5 -4
- package/corpus/templates/content/docs/product/capabilities/content.object.blocks-field.md +9 -4
- package/corpus/templates/content/docs/product/encyclopedia.md +4 -4
- package/corpus/templates/content/parity/matrix.md +1 -1
- package/corpus/templates/content/parity/matrix.ts +2 -0
- package/corpus/templates/content/server/db/schema.ts +100 -3
- package/corpus/templates/content/server/plugins/db.ts +85 -0
- package/corpus/templates/content/shared/api.ts +78 -2
- package/corpus/templates/content/shared/blocks-field-identity.ts +554 -0
- package/corpus/templates/content/shared/document-export.ts +33 -1
- package/dist/action.d.ts +22 -0
- package/dist/action.js +27 -0
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/routes.d.ts +1 -1
- package/dist/extensions/url-safety.d.ts +7 -2
- package/dist/extensions/url-safety.js +27 -12
- package/dist/index.browser.d.ts +1 -1
- package/dist/index.browser.js +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/mcp-client/oauth-client.d.ts +16 -1
- package/dist/mcp-client/oauth-client.js +177 -83
- package/dist/mcp-client/remote-store.js +46 -20
- package/dist/notifications/routes.d.ts +1 -1
- package/dist/oauth-tokens/index.d.ts +2 -1
- package/dist/oauth-tokens/index.js +2 -1
- package/dist/oauth-tokens/lifecycle.d.ts +87 -0
- package/dist/oauth-tokens/lifecycle.js +434 -0
- package/dist/oauth-tokens/migrations.js +10 -0
- package/dist/oauth-tokens/store.d.ts +22 -0
- package/dist/oauth-tokens/store.js +141 -18
- package/dist/observability/routes.d.ts +2 -2
- package/dist/progress/routes.d.ts +1 -1
- package/dist/secrets/routes.d.ts +6 -6
- package/dist/server/action-routes.js +10 -2
- package/dist/server/realtime-token.d.ts +1 -1
- package/package.json +2 -1
|
@@ -38,12 +38,17 @@ import {
|
|
|
38
38
|
parsePropertyValue,
|
|
39
39
|
type DocumentPropertyType,
|
|
40
40
|
} from "../shared/properties.js";
|
|
41
|
+
import { deleteBlocksFieldIdentity } from "./_blocks-field-identity.js";
|
|
41
42
|
import { favoriteDocumentIds } from "./_content-favorites.js";
|
|
42
43
|
import {
|
|
43
44
|
listContentOrganizationMemberships,
|
|
44
45
|
normalizeContentSpaceEmail,
|
|
45
46
|
resolveContentSpaceAccess,
|
|
46
47
|
} from "./_content-space-access.js";
|
|
48
|
+
import {
|
|
49
|
+
databaseRowRevision,
|
|
50
|
+
getDatabaseMutationContract,
|
|
51
|
+
} from "./_database-row-mutation.js";
|
|
47
52
|
import { getAllContentDatabaseSourceSnapshots } from "./_database-source-utils.js";
|
|
48
53
|
import {
|
|
49
54
|
applyFederatedOverlayValues,
|
|
@@ -1049,6 +1054,21 @@ export async function getContentDatabasePageResponse(
|
|
|
1049
1054
|
queued: bodyHydrationQueued,
|
|
1050
1055
|
}),
|
|
1051
1056
|
properties: propertiesByDocumentId.get(document.id) ?? [],
|
|
1057
|
+
rowRevision: databaseRowRevision({
|
|
1058
|
+
itemId: item.id,
|
|
1059
|
+
documentId: document.id,
|
|
1060
|
+
title: document.title,
|
|
1061
|
+
values: (propertiesByDocumentId.get(document.id) ?? [])
|
|
1062
|
+
.filter(
|
|
1063
|
+
(property) =>
|
|
1064
|
+
!isBlocksPropertyType(property.definition.type) &&
|
|
1065
|
+
!isComputedPropertyType(property.definition.type),
|
|
1066
|
+
)
|
|
1067
|
+
.map((property) => ({
|
|
1068
|
+
propertyId: property.definition.id,
|
|
1069
|
+
value: property.value,
|
|
1070
|
+
})),
|
|
1071
|
+
}),
|
|
1052
1072
|
});
|
|
1053
1073
|
}
|
|
1054
1074
|
|
|
@@ -1194,6 +1214,26 @@ export async function getContentDatabaseResponse(
|
|
|
1194
1214
|
sources: page.sources,
|
|
1195
1215
|
pagination: page.pagination,
|
|
1196
1216
|
tableQueryMode: page.tableQueryMode,
|
|
1217
|
+
mutationContract:
|
|
1218
|
+
page.databaseRecord.spaceId && !page.databaseRecord.systemRole
|
|
1219
|
+
? await getDatabaseMutationContract(
|
|
1220
|
+
{
|
|
1221
|
+
authorityScope: page.databaseRecord.orgId
|
|
1222
|
+
? {
|
|
1223
|
+
kind: "organization",
|
|
1224
|
+
id: page.databaseRecord.orgId,
|
|
1225
|
+
}
|
|
1226
|
+
: {
|
|
1227
|
+
kind: "personal",
|
|
1228
|
+
id: page.databaseRecord.ownerEmail,
|
|
1229
|
+
},
|
|
1230
|
+
spaceId: page.databaseRecord.spaceId,
|
|
1231
|
+
databaseId: page.databaseRecord.id,
|
|
1232
|
+
databaseDocumentId: page.databaseRecord.documentId,
|
|
1233
|
+
},
|
|
1234
|
+
{ accessAlreadyResolved: true },
|
|
1235
|
+
)
|
|
1236
|
+
: undefined,
|
|
1197
1237
|
};
|
|
1198
1238
|
}
|
|
1199
1239
|
|
|
@@ -1417,15 +1457,21 @@ export async function deleteDatabaseDataForDocument(
|
|
|
1417
1457
|
.from(schema.documentPropertyDefinitions)
|
|
1418
1458
|
.where(eq(schema.documentPropertyDefinitions.databaseId, database.id));
|
|
1419
1459
|
|
|
1420
|
-
|
|
1460
|
+
const definitionIds = definitions.map((definition) => definition.id);
|
|
1461
|
+
if (definitionIds.length > 0) {
|
|
1462
|
+
await deleteBlocksFieldIdentity({ db, propertyIds: definitionIds });
|
|
1421
1463
|
await db
|
|
1422
1464
|
.delete(schema.documentPropertyValues)
|
|
1423
|
-
.where(
|
|
1465
|
+
.where(
|
|
1466
|
+
inArray(schema.documentPropertyValues.propertyId, definitionIds),
|
|
1467
|
+
);
|
|
1424
1468
|
// Independent Blocks-field content is keyed by property id; drop it so
|
|
1425
1469
|
// deleting a database leaves no orphaned document_block_field_contents.
|
|
1426
1470
|
await db
|
|
1427
1471
|
.delete(schema.documentBlockFieldContents)
|
|
1428
|
-
.where(
|
|
1472
|
+
.where(
|
|
1473
|
+
inArray(schema.documentBlockFieldContents.propertyId, definitionIds),
|
|
1474
|
+
);
|
|
1429
1475
|
}
|
|
1430
1476
|
const sources = await db
|
|
1431
1477
|
.select({ id: schema.contentDatabaseSources.id })
|
|
@@ -1463,6 +1509,11 @@ export async function deleteDatabaseDataForDocument(
|
|
|
1463
1509
|
.where(
|
|
1464
1510
|
eq(schema.contentDatabaseMigrationReceipts.databaseId, database.id),
|
|
1465
1511
|
);
|
|
1512
|
+
await db
|
|
1513
|
+
.delete(schema.contentDatabaseRowMutationReceipts)
|
|
1514
|
+
.where(
|
|
1515
|
+
eq(schema.contentDatabaseRowMutationReceipts.databaseId, database.id),
|
|
1516
|
+
);
|
|
1466
1517
|
await db
|
|
1467
1518
|
.delete(schema.documentPropertyDefinitions)
|
|
1468
1519
|
.where(eq(schema.documentPropertyDefinitions.databaseId, database.id));
|
|
@@ -1482,6 +1533,7 @@ export async function deleteDatabaseDataForDocument(
|
|
|
1482
1533
|
db,
|
|
1483
1534
|
);
|
|
1484
1535
|
if (item) {
|
|
1536
|
+
await deleteBlocksFieldIdentity({ db, documentId });
|
|
1485
1537
|
await db
|
|
1486
1538
|
.delete(schema.contentDatabaseItemKeyClaims)
|
|
1487
1539
|
.where(eq(schema.contentDatabaseItemKeyClaims.documentId, documentId));
|
|
@@ -1,10 +1,15 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
accessFilter,
|
|
3
|
+
assertAccess,
|
|
4
|
+
resolveAccess,
|
|
5
|
+
} from "@agent-native/core/sharing";
|
|
2
6
|
import {
|
|
3
7
|
and,
|
|
4
8
|
asc,
|
|
5
9
|
eq,
|
|
6
10
|
inArray,
|
|
7
11
|
isNull,
|
|
12
|
+
or,
|
|
8
13
|
sql,
|
|
9
14
|
type InferSelectModel,
|
|
10
15
|
} from "drizzle-orm";
|
|
@@ -21,6 +26,7 @@ import type {
|
|
|
21
26
|
ContentDatabaseOpenPagesIn,
|
|
22
27
|
DocumentProperty,
|
|
23
28
|
} from "../shared/api.js";
|
|
29
|
+
import { blocksFieldId } from "../shared/blocks-field-identity.js";
|
|
24
30
|
import {
|
|
25
31
|
DEFAULT_BLOCKS_FIELD_NAME,
|
|
26
32
|
defaultPropertyOptions,
|
|
@@ -42,6 +48,7 @@ import {
|
|
|
42
48
|
type DocumentPropertyValue,
|
|
43
49
|
} from "../shared/properties.js";
|
|
44
50
|
import { chunks } from "./_batch-utils.js";
|
|
51
|
+
import { readBlocksFieldIdentities } from "./_blocks-field-identity.js";
|
|
45
52
|
import {
|
|
46
53
|
propertyDefinitionsPositionScope,
|
|
47
54
|
withPositionLock,
|
|
@@ -58,7 +65,8 @@ type ContentDatabaseSummaryRow = Pick<
|
|
|
58
65
|
| "viewConfigJson"
|
|
59
66
|
| "createdAt"
|
|
60
67
|
| "updatedAt"
|
|
61
|
-
|
|
68
|
+
> &
|
|
69
|
+
Partial<Pick<ContentDatabaseRow, "spaceId" | "naturalKeyPropertyId">>;
|
|
62
70
|
type ContentDatabaseItemRow = InferSelectModel<
|
|
63
71
|
typeof schema.contentDatabaseItems
|
|
64
72
|
>;
|
|
@@ -192,8 +200,10 @@ export function serializeDatabase(
|
|
|
192
200
|
return {
|
|
193
201
|
id: database.id,
|
|
194
202
|
documentId: database.documentId,
|
|
203
|
+
spaceId: database.spaceId,
|
|
195
204
|
title: database.title,
|
|
196
205
|
systemRole: database.systemRole,
|
|
206
|
+
naturalKeyPropertyId: database.naturalKeyPropertyId,
|
|
197
207
|
description,
|
|
198
208
|
viewConfig: parseDatabaseViewConfig(database.viewConfigJson),
|
|
199
209
|
createdAt: database.createdAt,
|
|
@@ -506,6 +516,44 @@ export async function listPropertiesForDocument(
|
|
|
506
516
|
return listPropertiesForDatabase(database.id, document);
|
|
507
517
|
}
|
|
508
518
|
|
|
519
|
+
export async function listPropertiesForAllDocumentDatabases(
|
|
520
|
+
document: DocumentRow,
|
|
521
|
+
) {
|
|
522
|
+
const db = getDb();
|
|
523
|
+
const memberships = await db
|
|
524
|
+
.select({ databaseId: schema.contentDatabaseItems.databaseId })
|
|
525
|
+
.from(schema.contentDatabaseItems)
|
|
526
|
+
.where(eq(schema.contentDatabaseItems.documentId, document.id));
|
|
527
|
+
const membershipIds = memberships.map((membership) => membership.databaseId);
|
|
528
|
+
const databases = await db
|
|
529
|
+
.select({
|
|
530
|
+
id: schema.contentDatabases.id,
|
|
531
|
+
documentId: schema.contentDatabases.documentId,
|
|
532
|
+
})
|
|
533
|
+
.from(schema.contentDatabases)
|
|
534
|
+
.where(
|
|
535
|
+
and(
|
|
536
|
+
isNull(schema.contentDatabases.deletedAt),
|
|
537
|
+
membershipIds.length > 0
|
|
538
|
+
? or(
|
|
539
|
+
eq(schema.contentDatabases.documentId, document.id),
|
|
540
|
+
inArray(schema.contentDatabases.id, membershipIds),
|
|
541
|
+
)
|
|
542
|
+
: eq(schema.contentDatabases.documentId, document.id),
|
|
543
|
+
),
|
|
544
|
+
)
|
|
545
|
+
.orderBy(asc(schema.contentDatabases.id));
|
|
546
|
+
|
|
547
|
+
const properties = [];
|
|
548
|
+
for (const database of databases) {
|
|
549
|
+
if (!(await resolveAccess("document", database.documentId))) continue;
|
|
550
|
+
properties.push(
|
|
551
|
+
...(await listPropertiesForDatabase(database.id, document)),
|
|
552
|
+
);
|
|
553
|
+
}
|
|
554
|
+
return properties;
|
|
555
|
+
}
|
|
556
|
+
|
|
509
557
|
export async function listPropertiesForDatabase(
|
|
510
558
|
databaseId: string,
|
|
511
559
|
valueDocument?: DocumentRow,
|
|
@@ -540,10 +588,46 @@ export async function listPropertiesForDatabase(
|
|
|
540
588
|
? await blockFieldContentsForDocument(valueDocument.id)
|
|
541
589
|
: new Map<string, string>();
|
|
542
590
|
|
|
591
|
+
const blocksFieldIdentityById = valueDocument
|
|
592
|
+
? await readBlocksFieldIdentities({
|
|
593
|
+
db,
|
|
594
|
+
fields: definitions.flatMap((definition) => {
|
|
595
|
+
const type = definition.type as DocumentPropertyType;
|
|
596
|
+
if (!isBlocksPropertyType(type)) return [];
|
|
597
|
+
const options = parsePropertyOptions(definition.optionsJson);
|
|
598
|
+
return [
|
|
599
|
+
{
|
|
600
|
+
documentId: valueDocument.id,
|
|
601
|
+
propertyId: definition.id,
|
|
602
|
+
markdown: resolveBlocksFieldValue({
|
|
603
|
+
options,
|
|
604
|
+
documentBody: valueDocument.content,
|
|
605
|
+
blockFieldContent: blockContentByPropertyId.get(definition.id),
|
|
606
|
+
}),
|
|
607
|
+
},
|
|
608
|
+
];
|
|
609
|
+
}),
|
|
610
|
+
})
|
|
611
|
+
: new Map();
|
|
612
|
+
|
|
543
613
|
const properties = definitions.map((definition) => {
|
|
544
614
|
const type = definition.type as DocumentPropertyType;
|
|
545
615
|
const storedValue = valueByPropertyId.get(definition.id);
|
|
546
616
|
const options = parsePropertyOptions(definition.optionsJson);
|
|
617
|
+
const value =
|
|
618
|
+
valueDocument && isComputedPropertyType(type) && type !== "formula"
|
|
619
|
+
? computedPropertyValue(type, valueDocument, {
|
|
620
|
+
databaseRowNumber: rowNumberByDocumentId.get(valueDocument.id),
|
|
621
|
+
})
|
|
622
|
+
: valueDocument && isBlocksPropertyType(type)
|
|
623
|
+
? // Each Blocks field reads from exactly one place: the primary from
|
|
624
|
+
// the document body, additional fields from their own store.
|
|
625
|
+
resolveBlocksFieldValue({
|
|
626
|
+
options,
|
|
627
|
+
documentBody: valueDocument.content,
|
|
628
|
+
blockFieldContent: blockContentByPropertyId.get(definition.id),
|
|
629
|
+
})
|
|
630
|
+
: parsePropertyValue(storedValue?.valueJson);
|
|
547
631
|
return {
|
|
548
632
|
definition: {
|
|
549
633
|
id: definition.id,
|
|
@@ -560,21 +644,15 @@ export async function listPropertiesForDatabase(
|
|
|
560
644
|
createdAt: definition.createdAt,
|
|
561
645
|
updatedAt: definition.updatedAt,
|
|
562
646
|
},
|
|
563
|
-
value
|
|
564
|
-
valueDocument && isComputedPropertyType(type) && type !== "formula"
|
|
565
|
-
? computedPropertyValue(type, valueDocument, {
|
|
566
|
-
databaseRowNumber: rowNumberByDocumentId.get(valueDocument.id),
|
|
567
|
-
})
|
|
568
|
-
: valueDocument && isBlocksPropertyType(type)
|
|
569
|
-
? // Each Blocks field reads from exactly one place: the primary from
|
|
570
|
-
// the document body, additional fields from their own store.
|
|
571
|
-
resolveBlocksFieldValue({
|
|
572
|
-
options,
|
|
573
|
-
documentBody: valueDocument.content,
|
|
574
|
-
blockFieldContent: blockContentByPropertyId.get(definition.id),
|
|
575
|
-
})
|
|
576
|
-
: parsePropertyValue(storedValue?.valueJson),
|
|
647
|
+
value,
|
|
577
648
|
editable: !definition.systemRole && !isComputedPropertyType(type),
|
|
649
|
+
...(valueDocument && isBlocksPropertyType(type)
|
|
650
|
+
? {
|
|
651
|
+
blocksField: blocksFieldIdentityById.get(
|
|
652
|
+
blocksFieldId(valueDocument.id, definition.id),
|
|
653
|
+
),
|
|
654
|
+
}
|
|
655
|
+
: {}),
|
|
578
656
|
};
|
|
579
657
|
});
|
|
580
658
|
|
|
@@ -724,32 +802,64 @@ export async function listPropertiesForDatabaseDocuments(
|
|
|
724
802
|
}
|
|
725
803
|
}
|
|
726
804
|
|
|
805
|
+
const blocksFieldIdentityById = await readBlocksFieldIdentities({
|
|
806
|
+
db,
|
|
807
|
+
fields: valueDocuments.flatMap((document) =>
|
|
808
|
+
definitions.flatMap((definition) => {
|
|
809
|
+
const type = definition.type as DocumentPropertyType;
|
|
810
|
+
if (!isBlocksPropertyType(type)) return [];
|
|
811
|
+
const options = parsePropertyOptions(definition.optionsJson);
|
|
812
|
+
return [
|
|
813
|
+
{
|
|
814
|
+
documentId: document.id,
|
|
815
|
+
propertyId: definition.id,
|
|
816
|
+
markdown: resolveBlocksFieldValue({
|
|
817
|
+
options,
|
|
818
|
+
documentBody: document.content,
|
|
819
|
+
blockFieldContent: blockContentByDocumentAndProperty.get(
|
|
820
|
+
propertyValueKey(document.id, definition.id),
|
|
821
|
+
),
|
|
822
|
+
}),
|
|
823
|
+
},
|
|
824
|
+
];
|
|
825
|
+
}),
|
|
826
|
+
),
|
|
827
|
+
});
|
|
828
|
+
|
|
727
829
|
for (const document of valueDocuments) {
|
|
728
830
|
const properties = definitions.map((definition) => {
|
|
729
831
|
const propertyDefinition = serializePropertyDefinition(definition);
|
|
730
832
|
const storedValue = valueByDocumentAndProperty.get(
|
|
731
833
|
propertyValueKey(document.id, definition.id),
|
|
732
834
|
);
|
|
835
|
+
const value =
|
|
836
|
+
isComputedPropertyType(propertyDefinition.type) &&
|
|
837
|
+
propertyDefinition.type !== "formula"
|
|
838
|
+
? computedPropertyValue(propertyDefinition.type, document, {
|
|
839
|
+
databaseRowNumber: rowNumberByDocumentId.get(document.id),
|
|
840
|
+
})
|
|
841
|
+
: isBlocksPropertyType(propertyDefinition.type)
|
|
842
|
+
? resolveBlocksFieldValue({
|
|
843
|
+
options: propertyDefinition.options,
|
|
844
|
+
documentBody: document.content,
|
|
845
|
+
blockFieldContent: blockContentByDocumentAndProperty.get(
|
|
846
|
+
propertyValueKey(document.id, definition.id),
|
|
847
|
+
),
|
|
848
|
+
})
|
|
849
|
+
: parsePropertyValue(storedValue?.valueJson);
|
|
733
850
|
return {
|
|
734
851
|
definition: propertyDefinition,
|
|
735
|
-
value
|
|
736
|
-
isComputedPropertyType(propertyDefinition.type) &&
|
|
737
|
-
propertyDefinition.type !== "formula"
|
|
738
|
-
? computedPropertyValue(propertyDefinition.type, document, {
|
|
739
|
-
databaseRowNumber: rowNumberByDocumentId.get(document.id),
|
|
740
|
-
})
|
|
741
|
-
: isBlocksPropertyType(propertyDefinition.type)
|
|
742
|
-
? resolveBlocksFieldValue({
|
|
743
|
-
options: propertyDefinition.options,
|
|
744
|
-
documentBody: document.content,
|
|
745
|
-
blockFieldContent: blockContentByDocumentAndProperty.get(
|
|
746
|
-
propertyValueKey(document.id, definition.id),
|
|
747
|
-
),
|
|
748
|
-
})
|
|
749
|
-
: parsePropertyValue(storedValue?.valueJson),
|
|
852
|
+
value,
|
|
750
853
|
editable:
|
|
751
854
|
!definition.systemRole &&
|
|
752
855
|
!isComputedPropertyType(propertyDefinition.type),
|
|
856
|
+
...(isBlocksPropertyType(propertyDefinition.type)
|
|
857
|
+
? {
|
|
858
|
+
blocksField: blocksFieldIdentityById.get(
|
|
859
|
+
blocksFieldId(document.id, definition.id),
|
|
860
|
+
),
|
|
861
|
+
}
|
|
862
|
+
: {}),
|
|
753
863
|
};
|
|
754
864
|
});
|
|
755
865
|
|
|
@@ -1,255 +1,77 @@
|
|
|
1
1
|
import { defineAction } from "@agent-native/core";
|
|
2
|
-
import {
|
|
3
|
-
import { getRequestUserEmail } from "@agent-native/core/server/request-context";
|
|
4
|
-
import { assertAccess } from "@agent-native/core/sharing";
|
|
5
|
-
import { and, eq, inArray, isNull, sql } from "drizzle-orm";
|
|
2
|
+
import { buildDeepLink } from "@agent-native/core/server";
|
|
6
3
|
import { z } from "zod";
|
|
7
4
|
|
|
8
|
-
import {
|
|
5
|
+
import type { ContentDatabaseRowMutationResult } from "../shared/api.js";
|
|
9
6
|
import {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
} from "
|
|
13
|
-
import {
|
|
14
|
-
lockContentDatabaseMutation,
|
|
15
|
-
touchContentDatabase,
|
|
16
|
-
} from "./_content-database-mutation-lock.js";
|
|
17
|
-
import { ensureDocumentFilesMembership } from "./_content-files.js";
|
|
7
|
+
createDatabaseRow,
|
|
8
|
+
databaseMutationEnvelopeSchema,
|
|
9
|
+
} from "./_database-row-mutation.js";
|
|
18
10
|
import { getContentDatabaseResponse } from "./_database-utils.js";
|
|
19
|
-
import {
|
|
20
|
-
databaseItemsPositionScope,
|
|
21
|
-
documentsPositionScope,
|
|
22
|
-
withPositionLock,
|
|
23
|
-
} from "./_position-utils.js";
|
|
24
|
-
import { nanoid, normalizedValueJson } from "./_property-utils.js";
|
|
25
11
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
.select()
|
|
40
|
-
.from(schema.contentDatabases)
|
|
41
|
-
.where(
|
|
42
|
-
and(
|
|
43
|
-
eq(schema.contentDatabases.id, databaseId),
|
|
44
|
-
isNull(schema.contentDatabases.deletedAt),
|
|
45
|
-
),
|
|
46
|
-
);
|
|
47
|
-
if (!database) throw new Error(`Database "${databaseId}" not found`);
|
|
48
|
-
if (database.systemRole === "workspaces") {
|
|
49
|
-
throw new Error("Use create-content-space to add a workspace");
|
|
50
|
-
}
|
|
12
|
+
const schema = databaseMutationEnvelopeSchema.extend({
|
|
13
|
+
title: z
|
|
14
|
+
.string()
|
|
15
|
+
.trim()
|
|
16
|
+
.min(1)
|
|
17
|
+
.max(500)
|
|
18
|
+
.optional()
|
|
19
|
+
.describe("New row page title"),
|
|
20
|
+
propertyValues: z
|
|
21
|
+
.record(z.string(), z.unknown())
|
|
22
|
+
.optional()
|
|
23
|
+
.describe("Strict property values keyed by property definition ID"),
|
|
24
|
+
});
|
|
51
25
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
26
|
+
export default defineAction({
|
|
27
|
+
description:
|
|
28
|
+
"Create one row in an exact ordinary Content database using its discovered schema revision. Strictly validates every non-Blocks property, applies the side effect once per idempotency key, and returns a verified receipt with stable row identity.",
|
|
29
|
+
schema,
|
|
30
|
+
audit: {
|
|
31
|
+
recordInputs: false,
|
|
32
|
+
target: (args) => ({
|
|
33
|
+
type: "content-database",
|
|
34
|
+
id: args.target.databaseId,
|
|
35
|
+
visibility: "private",
|
|
36
|
+
}),
|
|
37
|
+
summary: (_args, result) => {
|
|
38
|
+
const receipt = (result as ContentDatabaseRowMutationResult | null)
|
|
39
|
+
?.receipt;
|
|
40
|
+
return receipt
|
|
41
|
+
? `Created Content database row ${receipt.row.itemId}`
|
|
42
|
+
: "Created Content database row";
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
run: async (args): Promise<ContentDatabaseRowMutationResult> => {
|
|
46
|
+
const result = await createDatabaseRow(args);
|
|
47
|
+
const response = await getContentDatabaseResponse(
|
|
48
|
+
result.receipt.target.databaseId,
|
|
49
|
+
{
|
|
50
|
+
limit: 1,
|
|
51
|
+
offset: 0,
|
|
52
|
+
documentIds: [result.receipt.row.documentId],
|
|
53
|
+
},
|
|
56
54
|
);
|
|
57
|
-
const
|
|
58
|
-
if (
|
|
59
|
-
database.spaceId &&
|
|
60
|
-
databaseDocument.spaceId &&
|
|
61
|
-
databaseDocument.spaceId !== database.spaceId
|
|
62
|
-
) {
|
|
55
|
+
const createdItem = response.items[0];
|
|
56
|
+
if (!createdItem || createdItem.id !== result.receipt.row.itemId) {
|
|
63
57
|
throw new Error(
|
|
64
|
-
|
|
58
|
+
"Created row receipt did not resolve to its exact read-back.",
|
|
65
59
|
);
|
|
66
60
|
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
if (databaseSpaceId && (!database.spaceId || !databaseDocument.spaceId)) {
|
|
74
|
-
await db.transaction(async (tx) => {
|
|
75
|
-
if (!database.spaceId) {
|
|
76
|
-
await tx
|
|
77
|
-
.update(schema.contentDatabases)
|
|
78
|
-
.set({ spaceId: databaseSpaceId, updatedAt: now })
|
|
79
|
-
.where(eq(schema.contentDatabases.id, databaseId));
|
|
80
|
-
}
|
|
81
|
-
if (!databaseDocument.spaceId) {
|
|
82
|
-
await tx
|
|
83
|
-
.update(schema.documents)
|
|
84
|
-
.set({ spaceId: databaseSpaceId, updatedAt: now })
|
|
85
|
-
.where(eq(schema.documents.id, database.documentId));
|
|
86
|
-
}
|
|
87
|
-
await ensureDocumentFilesMembership(tx, database.documentId, now);
|
|
88
|
-
});
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
const documentId = nanoid();
|
|
92
|
-
const itemId = nanoid();
|
|
93
|
-
|
|
94
|
-
const inheritedShares = await db
|
|
95
|
-
.select({
|
|
96
|
-
principalType: schema.documentShares.principalType,
|
|
97
|
-
principalId: schema.documentShares.principalId,
|
|
98
|
-
role: schema.documentShares.role,
|
|
99
|
-
})
|
|
100
|
-
.from(schema.documentShares)
|
|
101
|
-
.where(eq(schema.documentShares.resourceId, database.documentId));
|
|
102
|
-
|
|
103
|
-
const initialValues = Object.entries(propertyValues ?? {});
|
|
104
|
-
|
|
105
|
-
await withPositionLock(
|
|
106
|
-
documentsPositionScope(database.ownerEmail, database.documentId),
|
|
107
|
-
() =>
|
|
108
|
-
withPositionLock(databaseItemsPositionScope(databaseId), async () => {
|
|
109
|
-
await db.transaction(async (tx) => {
|
|
110
|
-
await lockContentDatabaseMutation(
|
|
111
|
-
tx as unknown as ReturnType<typeof getDb>,
|
|
112
|
-
databaseId,
|
|
113
|
-
);
|
|
114
|
-
await touchContentDatabase(
|
|
115
|
-
tx as unknown as ReturnType<typeof getDb>,
|
|
116
|
-
databaseId,
|
|
117
|
-
now,
|
|
118
|
-
);
|
|
119
|
-
const propertyValueRows: Array<
|
|
120
|
-
typeof schema.documentPropertyValues.$inferInsert
|
|
121
|
-
> = [];
|
|
122
|
-
if (initialValues.length > 0) {
|
|
123
|
-
const requestedPropertyIds = initialValues.map(
|
|
124
|
-
([propertyId]) => propertyId,
|
|
125
|
-
);
|
|
126
|
-
const definitions = await tx
|
|
127
|
-
.select()
|
|
128
|
-
.from(schema.documentPropertyDefinitions)
|
|
129
|
-
.where(
|
|
130
|
-
and(
|
|
131
|
-
eq(
|
|
132
|
-
schema.documentPropertyDefinitions.ownerEmail,
|
|
133
|
-
database.ownerEmail,
|
|
134
|
-
),
|
|
135
|
-
eq(
|
|
136
|
-
schema.documentPropertyDefinitions.databaseId,
|
|
137
|
-
databaseId,
|
|
138
|
-
),
|
|
139
|
-
inArray(
|
|
140
|
-
schema.documentPropertyDefinitions.id,
|
|
141
|
-
requestedPropertyIds,
|
|
142
|
-
),
|
|
143
|
-
),
|
|
144
|
-
);
|
|
145
|
-
const definitionById = new Map(
|
|
146
|
-
definitions.map((definition) => [definition.id, definition]),
|
|
147
|
-
);
|
|
148
|
-
for (const [propertyId, value] of initialValues) {
|
|
149
|
-
const definition = definitionById.get(propertyId);
|
|
150
|
-
const type = definition?.type as
|
|
151
|
-
| DocumentPropertyType
|
|
152
|
-
| undefined;
|
|
153
|
-
if (!definition || !type || isComputedPropertyType(type))
|
|
154
|
-
continue;
|
|
155
|
-
propertyValueRows.push({
|
|
156
|
-
id: nanoid(),
|
|
157
|
-
ownerEmail: database.ownerEmail,
|
|
158
|
-
documentId,
|
|
159
|
-
propertyId,
|
|
160
|
-
valueJson: normalizedValueJson(type, value),
|
|
161
|
-
createdAt: now,
|
|
162
|
-
updatedAt: now,
|
|
163
|
-
});
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
const [maxDocPos] = await tx
|
|
167
|
-
.select({ max: sql<number>`COALESCE(MAX(position), -1)` })
|
|
168
|
-
.from(schema.documents)
|
|
169
|
-
.where(
|
|
170
|
-
and(
|
|
171
|
-
eq(schema.documents.ownerEmail, database.ownerEmail),
|
|
172
|
-
eq(schema.documents.parentId, database.documentId),
|
|
173
|
-
),
|
|
174
|
-
);
|
|
175
|
-
const [maxItemPos] = await tx
|
|
176
|
-
.select({ max: sql<number>`COALESCE(MAX(position), -1)` })
|
|
177
|
-
.from(schema.contentDatabaseItems)
|
|
178
|
-
.where(eq(schema.contentDatabaseItems.databaseId, databaseId));
|
|
179
|
-
|
|
180
|
-
await tx.insert(schema.documents).values({
|
|
181
|
-
id: documentId,
|
|
182
|
-
spaceId: databaseSpaceId,
|
|
183
|
-
ownerEmail: database.ownerEmail,
|
|
184
|
-
orgId: database.orgId,
|
|
185
|
-
parentId: database.documentId,
|
|
186
|
-
title: title?.trim() ?? "",
|
|
187
|
-
content: "",
|
|
188
|
-
icon: null,
|
|
189
|
-
position: (maxDocPos?.max ?? -1) + 1,
|
|
190
|
-
isFavorite: 0,
|
|
191
|
-
hideFromSearch: databaseDocument.hideFromSearch ?? 0,
|
|
192
|
-
visibility: databaseDocument.visibility ?? "private",
|
|
193
|
-
createdAt: now,
|
|
194
|
-
updatedAt: now,
|
|
195
|
-
});
|
|
196
|
-
await tx.insert(schema.contentDatabaseItems).values({
|
|
197
|
-
id: itemId,
|
|
198
|
-
ownerEmail: database.ownerEmail,
|
|
199
|
-
orgId: database.orgId,
|
|
200
|
-
databaseId,
|
|
201
|
-
documentId,
|
|
202
|
-
position: (maxItemPos?.max ?? -1) + 1,
|
|
203
|
-
createdAt: now,
|
|
204
|
-
updatedAt: now,
|
|
205
|
-
});
|
|
206
|
-
if (inheritedShares.length > 0) {
|
|
207
|
-
await tx.insert(schema.documentShares).values(
|
|
208
|
-
inheritedShares.map((share) => ({
|
|
209
|
-
id: nanoid(),
|
|
210
|
-
resourceId: documentId,
|
|
211
|
-
principalType: share.principalType,
|
|
212
|
-
principalId: share.principalId,
|
|
213
|
-
role: share.role,
|
|
214
|
-
createdBy: getRequestUserEmail() ?? database.ownerEmail,
|
|
215
|
-
createdAt: now,
|
|
216
|
-
})),
|
|
217
|
-
);
|
|
218
|
-
}
|
|
219
|
-
if (propertyValueRows.length > 0) {
|
|
220
|
-
await tx
|
|
221
|
-
.insert(schema.documentPropertyValues)
|
|
222
|
-
.values(propertyValueRows);
|
|
223
|
-
}
|
|
224
|
-
await ensureDocumentFilesMembership(tx, documentId, now);
|
|
225
|
-
});
|
|
226
|
-
}),
|
|
227
|
-
);
|
|
228
|
-
|
|
229
|
-
await writeAppState("refresh-signal", { ts: Date.now() }).catch(() => {
|
|
230
|
-
// The row is already committed; polling will reconcile if a concurrent
|
|
231
|
-
// SQLite writer briefly blocks this best-effort refresh hint.
|
|
232
|
-
});
|
|
233
|
-
|
|
234
|
-
const response = await getContentDatabaseResponse(databaseId, {
|
|
235
|
-
limit: 100,
|
|
236
|
-
offset: 0,
|
|
237
|
-
});
|
|
238
|
-
const createdItem =
|
|
239
|
-
response.items.find((item) => item.id === itemId) ??
|
|
240
|
-
(
|
|
241
|
-
await getContentDatabaseResponse(databaseId, {
|
|
242
|
-
limit: 1,
|
|
243
|
-
offset: 0,
|
|
244
|
-
documentIds: [documentId],
|
|
245
|
-
})
|
|
246
|
-
).items.find((item) => item.id === itemId);
|
|
61
|
+
return { ...result, createdItem };
|
|
62
|
+
},
|
|
63
|
+
link: ({ result }) => {
|
|
64
|
+
const documentId = (result as ContentDatabaseRowMutationResult | null)
|
|
65
|
+
?.receipt.row.documentId;
|
|
66
|
+
if (!documentId) return null;
|
|
247
67
|
return {
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
68
|
+
url: buildDeepLink({
|
|
69
|
+
app: "content",
|
|
70
|
+
view: "editor",
|
|
71
|
+
params: { documentId },
|
|
72
|
+
}),
|
|
73
|
+
label: "Open database row",
|
|
74
|
+
view: "editor",
|
|
253
75
|
};
|
|
254
76
|
},
|
|
255
77
|
});
|