@agent-native/core 0.154.3 → 0.154.4
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/actions/_blocks-field-identity.ts +464 -0
- package/corpus/templates/content/actions/_database-utils.ts +11 -3
- package/corpus/templates/content/actions/_property-utils.ts +137 -30
- package/corpus/templates/content/actions/configure-document-property.ts +5 -0
- package/corpus/templates/content/actions/delete-document-property.ts +38 -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 +62 -2
- package/corpus/templates/content/actions/update-document.ts +24 -0
- 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/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-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/server/db/schema.ts +61 -0
- package/corpus/templates/content/server/plugins/db.ts +55 -0
- package/corpus/templates/content/shared/api.ts +3 -0
- package/corpus/templates/content/shared/blocks-field-identity.ts +554 -0
- package/corpus/templates/content/shared/document-export.ts +33 -1
- package/dist/extensions/url-safety.d.ts +7 -2
- package/dist/extensions/url-safety.js +27 -12
- 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/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 +5 -5
- package/dist/progress/routes.d.ts +1 -1
- package/dist/secrets/routes.d.ts +9 -9
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/realtime-token.d.ts +1 -1
- package/package.json +2 -1
|
@@ -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,
|
|
@@ -506,6 +513,44 @@ export async function listPropertiesForDocument(
|
|
|
506
513
|
return listPropertiesForDatabase(database.id, document);
|
|
507
514
|
}
|
|
508
515
|
|
|
516
|
+
export async function listPropertiesForAllDocumentDatabases(
|
|
517
|
+
document: DocumentRow,
|
|
518
|
+
) {
|
|
519
|
+
const db = getDb();
|
|
520
|
+
const memberships = await db
|
|
521
|
+
.select({ databaseId: schema.contentDatabaseItems.databaseId })
|
|
522
|
+
.from(schema.contentDatabaseItems)
|
|
523
|
+
.where(eq(schema.contentDatabaseItems.documentId, document.id));
|
|
524
|
+
const membershipIds = memberships.map((membership) => membership.databaseId);
|
|
525
|
+
const databases = await db
|
|
526
|
+
.select({
|
|
527
|
+
id: schema.contentDatabases.id,
|
|
528
|
+
documentId: schema.contentDatabases.documentId,
|
|
529
|
+
})
|
|
530
|
+
.from(schema.contentDatabases)
|
|
531
|
+
.where(
|
|
532
|
+
and(
|
|
533
|
+
isNull(schema.contentDatabases.deletedAt),
|
|
534
|
+
membershipIds.length > 0
|
|
535
|
+
? or(
|
|
536
|
+
eq(schema.contentDatabases.documentId, document.id),
|
|
537
|
+
inArray(schema.contentDatabases.id, membershipIds),
|
|
538
|
+
)
|
|
539
|
+
: eq(schema.contentDatabases.documentId, document.id),
|
|
540
|
+
),
|
|
541
|
+
)
|
|
542
|
+
.orderBy(asc(schema.contentDatabases.id));
|
|
543
|
+
|
|
544
|
+
const properties = [];
|
|
545
|
+
for (const database of databases) {
|
|
546
|
+
if (!(await resolveAccess("document", database.documentId))) continue;
|
|
547
|
+
properties.push(
|
|
548
|
+
...(await listPropertiesForDatabase(database.id, document)),
|
|
549
|
+
);
|
|
550
|
+
}
|
|
551
|
+
return properties;
|
|
552
|
+
}
|
|
553
|
+
|
|
509
554
|
export async function listPropertiesForDatabase(
|
|
510
555
|
databaseId: string,
|
|
511
556
|
valueDocument?: DocumentRow,
|
|
@@ -540,10 +585,46 @@ export async function listPropertiesForDatabase(
|
|
|
540
585
|
? await blockFieldContentsForDocument(valueDocument.id)
|
|
541
586
|
: new Map<string, string>();
|
|
542
587
|
|
|
588
|
+
const blocksFieldIdentityById = valueDocument
|
|
589
|
+
? await readBlocksFieldIdentities({
|
|
590
|
+
db,
|
|
591
|
+
fields: definitions.flatMap((definition) => {
|
|
592
|
+
const type = definition.type as DocumentPropertyType;
|
|
593
|
+
if (!isBlocksPropertyType(type)) return [];
|
|
594
|
+
const options = parsePropertyOptions(definition.optionsJson);
|
|
595
|
+
return [
|
|
596
|
+
{
|
|
597
|
+
documentId: valueDocument.id,
|
|
598
|
+
propertyId: definition.id,
|
|
599
|
+
markdown: resolveBlocksFieldValue({
|
|
600
|
+
options,
|
|
601
|
+
documentBody: valueDocument.content,
|
|
602
|
+
blockFieldContent: blockContentByPropertyId.get(definition.id),
|
|
603
|
+
}),
|
|
604
|
+
},
|
|
605
|
+
];
|
|
606
|
+
}),
|
|
607
|
+
})
|
|
608
|
+
: new Map();
|
|
609
|
+
|
|
543
610
|
const properties = definitions.map((definition) => {
|
|
544
611
|
const type = definition.type as DocumentPropertyType;
|
|
545
612
|
const storedValue = valueByPropertyId.get(definition.id);
|
|
546
613
|
const options = parsePropertyOptions(definition.optionsJson);
|
|
614
|
+
const value =
|
|
615
|
+
valueDocument && isComputedPropertyType(type) && type !== "formula"
|
|
616
|
+
? computedPropertyValue(type, valueDocument, {
|
|
617
|
+
databaseRowNumber: rowNumberByDocumentId.get(valueDocument.id),
|
|
618
|
+
})
|
|
619
|
+
: valueDocument && isBlocksPropertyType(type)
|
|
620
|
+
? // Each Blocks field reads from exactly one place: the primary from
|
|
621
|
+
// the document body, additional fields from their own store.
|
|
622
|
+
resolveBlocksFieldValue({
|
|
623
|
+
options,
|
|
624
|
+
documentBody: valueDocument.content,
|
|
625
|
+
blockFieldContent: blockContentByPropertyId.get(definition.id),
|
|
626
|
+
})
|
|
627
|
+
: parsePropertyValue(storedValue?.valueJson);
|
|
547
628
|
return {
|
|
548
629
|
definition: {
|
|
549
630
|
id: definition.id,
|
|
@@ -560,21 +641,15 @@ export async function listPropertiesForDatabase(
|
|
|
560
641
|
createdAt: definition.createdAt,
|
|
561
642
|
updatedAt: definition.updatedAt,
|
|
562
643
|
},
|
|
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),
|
|
644
|
+
value,
|
|
577
645
|
editable: !definition.systemRole && !isComputedPropertyType(type),
|
|
646
|
+
...(valueDocument && isBlocksPropertyType(type)
|
|
647
|
+
? {
|
|
648
|
+
blocksField: blocksFieldIdentityById.get(
|
|
649
|
+
blocksFieldId(valueDocument.id, definition.id),
|
|
650
|
+
),
|
|
651
|
+
}
|
|
652
|
+
: {}),
|
|
578
653
|
};
|
|
579
654
|
});
|
|
580
655
|
|
|
@@ -724,32 +799,64 @@ export async function listPropertiesForDatabaseDocuments(
|
|
|
724
799
|
}
|
|
725
800
|
}
|
|
726
801
|
|
|
802
|
+
const blocksFieldIdentityById = await readBlocksFieldIdentities({
|
|
803
|
+
db,
|
|
804
|
+
fields: valueDocuments.flatMap((document) =>
|
|
805
|
+
definitions.flatMap((definition) => {
|
|
806
|
+
const type = definition.type as DocumentPropertyType;
|
|
807
|
+
if (!isBlocksPropertyType(type)) return [];
|
|
808
|
+
const options = parsePropertyOptions(definition.optionsJson);
|
|
809
|
+
return [
|
|
810
|
+
{
|
|
811
|
+
documentId: document.id,
|
|
812
|
+
propertyId: definition.id,
|
|
813
|
+
markdown: resolveBlocksFieldValue({
|
|
814
|
+
options,
|
|
815
|
+
documentBody: document.content,
|
|
816
|
+
blockFieldContent: blockContentByDocumentAndProperty.get(
|
|
817
|
+
propertyValueKey(document.id, definition.id),
|
|
818
|
+
),
|
|
819
|
+
}),
|
|
820
|
+
},
|
|
821
|
+
];
|
|
822
|
+
}),
|
|
823
|
+
),
|
|
824
|
+
});
|
|
825
|
+
|
|
727
826
|
for (const document of valueDocuments) {
|
|
728
827
|
const properties = definitions.map((definition) => {
|
|
729
828
|
const propertyDefinition = serializePropertyDefinition(definition);
|
|
730
829
|
const storedValue = valueByDocumentAndProperty.get(
|
|
731
830
|
propertyValueKey(document.id, definition.id),
|
|
732
831
|
);
|
|
832
|
+
const value =
|
|
833
|
+
isComputedPropertyType(propertyDefinition.type) &&
|
|
834
|
+
propertyDefinition.type !== "formula"
|
|
835
|
+
? computedPropertyValue(propertyDefinition.type, document, {
|
|
836
|
+
databaseRowNumber: rowNumberByDocumentId.get(document.id),
|
|
837
|
+
})
|
|
838
|
+
: isBlocksPropertyType(propertyDefinition.type)
|
|
839
|
+
? resolveBlocksFieldValue({
|
|
840
|
+
options: propertyDefinition.options,
|
|
841
|
+
documentBody: document.content,
|
|
842
|
+
blockFieldContent: blockContentByDocumentAndProperty.get(
|
|
843
|
+
propertyValueKey(document.id, definition.id),
|
|
844
|
+
),
|
|
845
|
+
})
|
|
846
|
+
: parsePropertyValue(storedValue?.valueJson);
|
|
733
847
|
return {
|
|
734
848
|
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),
|
|
849
|
+
value,
|
|
750
850
|
editable:
|
|
751
851
|
!definition.systemRole &&
|
|
752
852
|
!isComputedPropertyType(propertyDefinition.type),
|
|
853
|
+
...(isBlocksPropertyType(propertyDefinition.type)
|
|
854
|
+
? {
|
|
855
|
+
blocksField: blocksFieldIdentityById.get(
|
|
856
|
+
blocksFieldId(document.id, definition.id),
|
|
857
|
+
),
|
|
858
|
+
}
|
|
859
|
+
: {}),
|
|
753
860
|
};
|
|
754
861
|
});
|
|
755
862
|
|
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
normalizePropertyVisibility,
|
|
17
17
|
type DocumentPropertyType,
|
|
18
18
|
} from "../shared/properties.js";
|
|
19
|
+
import { deleteBlocksFieldIdentity } from "./_blocks-field-identity.js";
|
|
19
20
|
import { lockContentDatabaseMutation } from "./_content-database-mutation-lock.js";
|
|
20
21
|
import { lockDatabaseMemberships } from "./_database-membership-lock.js";
|
|
21
22
|
import {
|
|
@@ -233,6 +234,10 @@ export default defineAction({
|
|
|
233
234
|
) &&
|
|
234
235
|
!isBlocksPropertyType(type)
|
|
235
236
|
) {
|
|
237
|
+
await deleteBlocksFieldIdentity({
|
|
238
|
+
db: tx as unknown as ReturnType<typeof getDb>,
|
|
239
|
+
propertyId: args.id!,
|
|
240
|
+
});
|
|
236
241
|
await tx
|
|
237
242
|
.delete(schema.documentBlockFieldContents)
|
|
238
243
|
.where(
|
|
@@ -11,6 +11,10 @@ import {
|
|
|
11
11
|
parsePropertyOptions,
|
|
12
12
|
type DocumentPropertyType,
|
|
13
13
|
} from "../shared/properties.js";
|
|
14
|
+
import {
|
|
15
|
+
deleteBlocksFieldIdentity,
|
|
16
|
+
lockPrimaryBlocksFieldsForDocuments,
|
|
17
|
+
} from "./_blocks-field-identity.js";
|
|
14
18
|
import { lockContentDatabaseMutation } from "./_content-database-mutation-lock.js";
|
|
15
19
|
import { lockDatabaseMemberships } from "./_database-membership-lock.js";
|
|
16
20
|
import {
|
|
@@ -65,14 +69,6 @@ export default defineAction({
|
|
|
65
69
|
tx as unknown as ReturnType<typeof getDb>,
|
|
66
70
|
database.id,
|
|
67
71
|
);
|
|
68
|
-
const memberships = await tx
|
|
69
|
-
.select({ id: schema.contentDatabaseItems.id })
|
|
70
|
-
.from(schema.contentDatabaseItems)
|
|
71
|
-
.where(eq(schema.contentDatabaseItems.databaseId, database.id));
|
|
72
|
-
await lockDatabaseMemberships(
|
|
73
|
-
tx,
|
|
74
|
-
memberships.map((membership) => membership.id),
|
|
75
|
-
);
|
|
76
72
|
const [lockedDefinition] = await tx
|
|
77
73
|
.select()
|
|
78
74
|
.from(schema.documentPropertyDefinitions)
|
|
@@ -100,6 +96,25 @@ export default defineAction({
|
|
|
100
96
|
isPrimaryBlocksField(
|
|
101
97
|
parsePropertyOptions(lockedDefinition.optionsJson),
|
|
102
98
|
);
|
|
99
|
+
const items = await tx
|
|
100
|
+
.select({
|
|
101
|
+
id: schema.contentDatabaseItems.id,
|
|
102
|
+
documentId: schema.contentDatabaseItems.documentId,
|
|
103
|
+
})
|
|
104
|
+
.from(schema.contentDatabaseItems)
|
|
105
|
+
.where(eq(schema.contentDatabaseItems.databaseId, database.id));
|
|
106
|
+
const primaryFieldsByDocument = isPrimaryBlocks
|
|
107
|
+
? await lockPrimaryBlocksFieldsForDocuments(
|
|
108
|
+
tx as unknown as ReturnType<typeof getDb>,
|
|
109
|
+
items.map((item) => item.documentId),
|
|
110
|
+
)
|
|
111
|
+
: new Map();
|
|
112
|
+
if (!isPrimaryBlocks) {
|
|
113
|
+
await lockDatabaseMemberships(
|
|
114
|
+
tx,
|
|
115
|
+
items.map((item) => item.id),
|
|
116
|
+
);
|
|
117
|
+
}
|
|
103
118
|
|
|
104
119
|
await tx
|
|
105
120
|
.delete(schema.documentPropertyValues)
|
|
@@ -117,6 +132,10 @@ export default defineAction({
|
|
|
117
132
|
.where(eq(schema.documentPropertyDefinitions.id, propertyId));
|
|
118
133
|
|
|
119
134
|
if (isBlocks) {
|
|
135
|
+
await deleteBlocksFieldIdentity({
|
|
136
|
+
db: tx as unknown as ReturnType<typeof getDb>,
|
|
137
|
+
propertyId,
|
|
138
|
+
});
|
|
120
139
|
await tx
|
|
121
140
|
.delete(schema.documentBlockFieldContents)
|
|
122
141
|
.where(eq(schema.documentBlockFieldContents.propertyId, propertyId));
|
|
@@ -130,20 +149,22 @@ export default defineAction({
|
|
|
130
149
|
})
|
|
131
150
|
.where(eq(schema.contentDatabases.id, database.id));
|
|
132
151
|
|
|
133
|
-
const
|
|
134
|
-
.
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
152
|
+
const documentsWithoutSurvivingPrimary = items
|
|
153
|
+
.filter(
|
|
154
|
+
(item) =>
|
|
155
|
+
!(primaryFieldsByDocument.get(item.documentId) ?? []).some(
|
|
156
|
+
(field: { propertyId: string }) =>
|
|
157
|
+
field.propertyId !== propertyId,
|
|
158
|
+
),
|
|
159
|
+
)
|
|
160
|
+
.map((item) => item.documentId);
|
|
161
|
+
if (documentsWithoutSurvivingPrimary.length > 0) {
|
|
138
162
|
const now = new Date().toISOString();
|
|
139
163
|
await tx
|
|
140
164
|
.update(schema.documents)
|
|
141
165
|
.set({ content: "", updatedAt: now })
|
|
142
166
|
.where(
|
|
143
|
-
inArray(
|
|
144
|
-
schema.documents.id,
|
|
145
|
-
items.map((item) => item.documentId),
|
|
146
|
-
),
|
|
167
|
+
inArray(schema.documents.id, documentsWithoutSurvivingPrimary),
|
|
147
168
|
);
|
|
148
169
|
}
|
|
149
170
|
}
|
|
@@ -6,6 +6,7 @@ import { z } from "zod";
|
|
|
6
6
|
|
|
7
7
|
import { getDb, schema } from "../server/db/index.js";
|
|
8
8
|
import { chunks } from "./_batch-utils.js";
|
|
9
|
+
import { deleteBlocksFieldIdentity } from "./_blocks-field-identity.js";
|
|
9
10
|
import {
|
|
10
11
|
lockContentDatabaseMutation,
|
|
11
12
|
touchContentDatabase,
|
|
@@ -695,6 +696,7 @@ async function deleteCollectedDocuments(
|
|
|
695
696
|
});
|
|
696
697
|
|
|
697
698
|
await deleteWhereIn(propertyDefinitionIds, async (propertyIdBatch) => {
|
|
699
|
+
await deleteBlocksFieldIdentity({ db, propertyIds: propertyIdBatch });
|
|
698
700
|
await db
|
|
699
701
|
.delete(schema.documentPropertyValues)
|
|
700
702
|
.where(
|
|
@@ -730,6 +732,7 @@ async function deleteCollectedDocuments(
|
|
|
730
732
|
});
|
|
731
733
|
|
|
732
734
|
await deleteWhereIn(documentIds, async (documentIdBatch) => {
|
|
735
|
+
await deleteBlocksFieldIdentity({ db, documentIds: documentIdBatch });
|
|
733
736
|
await db
|
|
734
737
|
.delete(schema.contentDatabaseBodyHydrationQueue)
|
|
735
738
|
.where(
|
|
@@ -17,6 +17,10 @@ import { eq } from "drizzle-orm";
|
|
|
17
17
|
import { z } from "zod";
|
|
18
18
|
|
|
19
19
|
import { getDb, schema } from "../server/db/index.js";
|
|
20
|
+
import {
|
|
21
|
+
lockPrimaryBlocksFields,
|
|
22
|
+
persistBlocksFieldIdentity,
|
|
23
|
+
} from "./_blocks-field-identity.js";
|
|
20
24
|
|
|
21
25
|
interface TextEdit {
|
|
22
26
|
find: string;
|
|
@@ -235,11 +239,24 @@ export default defineAction({
|
|
|
235
239
|
// Persist. The fresh updatedAt is the signal the open editor uses to tell an
|
|
236
240
|
// intentional external edit apart from a stale autosave echo.
|
|
237
241
|
const db = getDb();
|
|
242
|
+
const now = new Date().toISOString();
|
|
238
243
|
await db.transaction(async (tx: any) => {
|
|
244
|
+
const primaryBlocksFields = await lockPrimaryBlocksFields(tx, id);
|
|
239
245
|
await tx
|
|
240
246
|
.update(schema.documents)
|
|
241
|
-
.set({ content, updatedAt:
|
|
247
|
+
.set({ content, updatedAt: now })
|
|
242
248
|
.where(eq(schema.documents.id, id));
|
|
249
|
+
for (const field of primaryBlocksFields) {
|
|
250
|
+
await persistBlocksFieldIdentity({
|
|
251
|
+
db: tx as unknown as ReturnType<typeof getDb>,
|
|
252
|
+
ownerEmail: field.ownerEmail,
|
|
253
|
+
documentId: id,
|
|
254
|
+
propertyId: field.propertyId,
|
|
255
|
+
previousMarkdown: existing.content ?? "",
|
|
256
|
+
markdown: content,
|
|
257
|
+
now,
|
|
258
|
+
});
|
|
259
|
+
}
|
|
243
260
|
if (creativeContext) {
|
|
244
261
|
await recordGenerationCreativeContext(
|
|
245
262
|
{
|
|
@@ -3,8 +3,14 @@ import { buildDeepLink } from "@agent-native/core/server";
|
|
|
3
3
|
import { resolveAccess } from "@agent-native/core/sharing";
|
|
4
4
|
import { z } from "zod";
|
|
5
5
|
|
|
6
|
+
import { blocksContentHash } from "../shared/blocks-field-identity.js";
|
|
6
7
|
import { buildDocumentExport } from "../shared/document-export.js";
|
|
8
|
+
import {
|
|
9
|
+
isBlocksPropertyType,
|
|
10
|
+
isPrimaryBlocksField,
|
|
11
|
+
} from "../shared/properties.js";
|
|
7
12
|
import "../server/db/index.js";
|
|
13
|
+
import { listPropertiesForAllDocumentDatabases } from "./_property-utils.js";
|
|
8
14
|
|
|
9
15
|
export default defineAction({
|
|
10
16
|
description:
|
|
@@ -33,12 +39,47 @@ export default defineAction({
|
|
|
33
39
|
if (!access) throw new Error(`Document "${id}" not found`);
|
|
34
40
|
|
|
35
41
|
const doc = access.resource;
|
|
42
|
+
const properties = await listPropertiesForAllDocumentDatabases(doc);
|
|
43
|
+
const blocksFields = properties
|
|
44
|
+
.filter((property) => isBlocksPropertyType(property.definition.type))
|
|
45
|
+
.map((property) => {
|
|
46
|
+
if (!property.definition.databaseId) {
|
|
47
|
+
throw new Error(
|
|
48
|
+
`Blocks field "${property.definition.id}" is not attached to a database`,
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
if (!property.blocksField) {
|
|
52
|
+
throw new Error(
|
|
53
|
+
`Blocks field "${property.definition.id}" has no identity state`,
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
const markdown =
|
|
57
|
+
content !== undefined &&
|
|
58
|
+
isPrimaryBlocksField(property.definition.options)
|
|
59
|
+
? content
|
|
60
|
+
: typeof property.value === "string"
|
|
61
|
+
? property.value
|
|
62
|
+
: "";
|
|
63
|
+
const identity =
|
|
64
|
+
blocksContentHash(markdown) === property.blocksField.contentHash
|
|
65
|
+
? property.blocksField
|
|
66
|
+
: { ...property.blocksField, identityStatus: "stale" as const };
|
|
67
|
+
return {
|
|
68
|
+
databaseId: property.definition.databaseId,
|
|
69
|
+
propertyId: property.definition.id,
|
|
70
|
+
name: property.definition.name,
|
|
71
|
+
position: property.definition.position,
|
|
72
|
+
markdown,
|
|
73
|
+
identity,
|
|
74
|
+
};
|
|
75
|
+
});
|
|
36
76
|
const payload = buildDocumentExport({
|
|
37
77
|
id: doc.id,
|
|
38
78
|
title: title ?? doc.title,
|
|
39
79
|
content: content ?? doc.content,
|
|
40
80
|
updatedAt: doc.updatedAt,
|
|
41
81
|
format,
|
|
82
|
+
blocksFields,
|
|
42
83
|
});
|
|
43
84
|
|
|
44
85
|
return {
|
|
@@ -4,6 +4,7 @@ import { assertAccess } from "@agent-native/core/sharing";
|
|
|
4
4
|
import { and, eq, inArray } from "drizzle-orm";
|
|
5
5
|
|
|
6
6
|
import { getDb, schema } from "../server/db/index.js";
|
|
7
|
+
import { deleteBlocksFieldIdentity } from "./_blocks-field-identity.js";
|
|
7
8
|
import {
|
|
8
9
|
lockContentDatabaseMutation,
|
|
9
10
|
touchContentDatabase,
|
|
@@ -132,6 +133,15 @@ export default defineAction({
|
|
|
132
133
|
)
|
|
133
134
|
).map((property) => property.id);
|
|
134
135
|
if (removedDocumentIds.length > 0 && propertyIds.length > 0) {
|
|
136
|
+
for (const removedDocumentId of removedDocumentIds) {
|
|
137
|
+
for (const propertyId of propertyIds) {
|
|
138
|
+
await deleteBlocksFieldIdentity({
|
|
139
|
+
db: tx as unknown as ReturnType<typeof getDb>,
|
|
140
|
+
documentId: removedDocumentId,
|
|
141
|
+
propertyId,
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
}
|
|
135
145
|
await tx
|
|
136
146
|
.delete(schema.documentPropertyValues)
|
|
137
147
|
.where(
|
|
@@ -12,6 +12,10 @@ import {
|
|
|
12
12
|
parsePropertyOptions,
|
|
13
13
|
type DocumentPropertyType,
|
|
14
14
|
} from "../shared/properties.js";
|
|
15
|
+
import {
|
|
16
|
+
lockPrimaryBlocksFields,
|
|
17
|
+
persistBlocksFieldIdentity,
|
|
18
|
+
} from "./_blocks-field-identity.js";
|
|
15
19
|
import { lockContentDatabaseMutation } from "./_content-database-mutation-lock.js";
|
|
16
20
|
import { resolveContentDocumentAccess } from "./_content-document-access.js";
|
|
17
21
|
import { lockDatabaseMemberships } from "./_database-membership-lock.js";
|
|
@@ -34,8 +38,15 @@ export default defineAction({
|
|
|
34
38
|
),
|
|
35
39
|
propertyId: z.string().describe("Property definition ID"),
|
|
36
40
|
value: z.unknown().describe("Value for the property type"),
|
|
41
|
+
expectedBlocksFieldRevision: z.number().int().nonnegative().optional(),
|
|
37
42
|
}),
|
|
38
|
-
run: async ({
|
|
43
|
+
run: async ({
|
|
44
|
+
documentId,
|
|
45
|
+
databaseId,
|
|
46
|
+
propertyId,
|
|
47
|
+
value,
|
|
48
|
+
expectedBlocksFieldRevision,
|
|
49
|
+
}) => {
|
|
39
50
|
const db = getDb();
|
|
40
51
|
const [definition] = await db
|
|
41
52
|
.select()
|
|
@@ -85,7 +96,10 @@ export default defineAction({
|
|
|
85
96
|
parsePropertyOptions(definition.optionsJson),
|
|
86
97
|
);
|
|
87
98
|
await db.transaction(async (tx) => {
|
|
88
|
-
await
|
|
99
|
+
const primaryBlocksFields = await lockPrimaryBlocksFields(
|
|
100
|
+
tx as unknown as ReturnType<typeof getDb>,
|
|
101
|
+
documentId,
|
|
102
|
+
);
|
|
89
103
|
const [lockedDefinition] = await tx
|
|
90
104
|
.select()
|
|
91
105
|
.from(schema.documentPropertyDefinitions)
|
|
@@ -116,12 +130,31 @@ export default defineAction({
|
|
|
116
130
|
target = blocksStorageTarget(
|
|
117
131
|
parsePropertyOptions(lockedDefinition.optionsJson),
|
|
118
132
|
);
|
|
133
|
+
let previousContent = "";
|
|
119
134
|
if (target === "document_body") {
|
|
135
|
+
const [currentDocument] = await tx
|
|
136
|
+
.select({ content: schema.documents.content })
|
|
137
|
+
.from(schema.documents)
|
|
138
|
+
.where(eq(schema.documents.id, documentId));
|
|
139
|
+
if (!currentDocument) {
|
|
140
|
+
throw new Error(`Document "${documentId}" not found`);
|
|
141
|
+
}
|
|
142
|
+
previousContent = currentDocument.content;
|
|
120
143
|
await tx
|
|
121
144
|
.update(schema.documents)
|
|
122
145
|
.set({ content, updatedAt: now })
|
|
123
146
|
.where(eq(schema.documents.id, documentId));
|
|
124
147
|
} else {
|
|
148
|
+
const [currentField] = await tx
|
|
149
|
+
.select({ content: schema.documentBlockFieldContents.content })
|
|
150
|
+
.from(schema.documentBlockFieldContents)
|
|
151
|
+
.where(
|
|
152
|
+
and(
|
|
153
|
+
eq(schema.documentBlockFieldContents.documentId, documentId),
|
|
154
|
+
eq(schema.documentBlockFieldContents.propertyId, propertyId),
|
|
155
|
+
),
|
|
156
|
+
);
|
|
157
|
+
previousContent = currentField?.content ?? "";
|
|
125
158
|
await tx
|
|
126
159
|
.insert(schema.documentBlockFieldContents)
|
|
127
160
|
.values({
|
|
@@ -141,6 +174,33 @@ export default defineAction({
|
|
|
141
174
|
set: { content, updatedAt: now },
|
|
142
175
|
});
|
|
143
176
|
}
|
|
177
|
+
const identityFields =
|
|
178
|
+
target === "document_body"
|
|
179
|
+
? primaryBlocksFields
|
|
180
|
+
: [{ propertyId, ownerEmail: database.ownerEmail }];
|
|
181
|
+
if (
|
|
182
|
+
target === "document_body" &&
|
|
183
|
+
!identityFields.some((field) => field.propertyId === propertyId)
|
|
184
|
+
) {
|
|
185
|
+
throw new Error(
|
|
186
|
+
"Primary Blocks membership changed before the operation completed.",
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
for (const field of identityFields) {
|
|
190
|
+
await persistBlocksFieldIdentity({
|
|
191
|
+
db: tx as unknown as ReturnType<typeof getDb>,
|
|
192
|
+
ownerEmail: field.ownerEmail,
|
|
193
|
+
documentId,
|
|
194
|
+
propertyId: field.propertyId,
|
|
195
|
+
previousMarkdown: previousContent,
|
|
196
|
+
markdown: content,
|
|
197
|
+
expectedRevision:
|
|
198
|
+
field.propertyId === propertyId
|
|
199
|
+
? expectedBlocksFieldRevision
|
|
200
|
+
: undefined,
|
|
201
|
+
now,
|
|
202
|
+
});
|
|
203
|
+
}
|
|
144
204
|
});
|
|
145
205
|
return {
|
|
146
206
|
documentId,
|
|
@@ -19,6 +19,10 @@ import {
|
|
|
19
19
|
parseDocumentHideFromSearch,
|
|
20
20
|
} from "../server/lib/documents.js";
|
|
21
21
|
import type { DocumentUpdateResponse } from "../shared/api.js";
|
|
22
|
+
import {
|
|
23
|
+
lockPrimaryBlocksFields,
|
|
24
|
+
persistBlocksFieldIdentity,
|
|
25
|
+
} from "./_blocks-field-identity.js";
|
|
22
26
|
import { BUILDER_CMS_BODY_CONTENT_KEY } from "./_builder-cms-source-adapter.js";
|
|
23
27
|
import { reconcileInlineDatabasesForDocument } from "./_content-database-lifecycle.js";
|
|
24
28
|
import { resolveContentDocumentAccess } from "./_content-document-access.js";
|
|
@@ -510,6 +514,12 @@ export default defineAction({
|
|
|
510
514
|
if (iconChanged) updates.icon = args.icon;
|
|
511
515
|
let contentCasConflict = false;
|
|
512
516
|
await db.transaction(async (tx) => {
|
|
517
|
+
const primaryBlocksFields = contentChanged
|
|
518
|
+
? await lockPrimaryBlocksFields(
|
|
519
|
+
tx as unknown as ReturnType<typeof getDb>,
|
|
520
|
+
id,
|
|
521
|
+
)
|
|
522
|
+
: [];
|
|
513
523
|
if (useContentCas) {
|
|
514
524
|
const applied = await tx
|
|
515
525
|
.update(schema.documents)
|
|
@@ -532,6 +542,20 @@ export default defineAction({
|
|
|
532
542
|
.where(eq(schema.documents.id, id));
|
|
533
543
|
}
|
|
534
544
|
|
|
545
|
+
if (contentChanged && content !== undefined) {
|
|
546
|
+
for (const field of primaryBlocksFields) {
|
|
547
|
+
await persistBlocksFieldIdentity({
|
|
548
|
+
db: tx as unknown as ReturnType<typeof getDb>,
|
|
549
|
+
ownerEmail: field.ownerEmail,
|
|
550
|
+
documentId: id,
|
|
551
|
+
propertyId: field.propertyId,
|
|
552
|
+
previousMarkdown: existing.content,
|
|
553
|
+
markdown: content,
|
|
554
|
+
now: updates.updatedAt as string,
|
|
555
|
+
});
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
|
|
535
559
|
if (titleChanged || contentChanged) {
|
|
536
560
|
const [latestVersion] = await tx
|
|
537
561
|
.select({ createdAt: schema.documentVersions.createdAt })
|