@agent-native/core 0.154.2 → 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/cli/create.js +3 -0
- package/dist/collab/awareness.d.ts +2 -2
- 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 +1 -1
- package/dist/resources/handlers.d.ts +1 -1
- package/package.json +2 -1
package/corpus/README.md
CHANGED
|
@@ -0,0 +1,464 @@
|
|
|
1
|
+
import { and, asc, eq, inArray } from "drizzle-orm";
|
|
2
|
+
|
|
3
|
+
import { getDb, schema } from "../server/db/index.js";
|
|
4
|
+
import {
|
|
5
|
+
blocksContentHash,
|
|
6
|
+
blocksFieldId,
|
|
7
|
+
exposeBlocksFieldIdentity,
|
|
8
|
+
legacyBlocksFieldIdentity,
|
|
9
|
+
materializeLegacyBlocksFieldIdentity,
|
|
10
|
+
reconcileBlocksFieldIdentity,
|
|
11
|
+
type BlocksFieldIdentity,
|
|
12
|
+
type StoredBlocksFieldIdentity,
|
|
13
|
+
} from "../shared/blocks-field-identity.js";
|
|
14
|
+
import { lockDatabaseMemberships } from "./_database-membership-lock.js";
|
|
15
|
+
|
|
16
|
+
type ContentDb = ReturnType<typeof getDb>;
|
|
17
|
+
|
|
18
|
+
export interface PrimaryBlocksField {
|
|
19
|
+
propertyId: string;
|
|
20
|
+
ownerEmail: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export class BlocksFieldRevisionConflictError extends Error {
|
|
24
|
+
readonly statusCode = 409;
|
|
25
|
+
|
|
26
|
+
constructor(message: string) {
|
|
27
|
+
super(message);
|
|
28
|
+
this.name = "BlocksFieldRevisionConflictError";
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function nanoid(size = 12): string {
|
|
33
|
+
const chars =
|
|
34
|
+
"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
|
|
35
|
+
let id = "";
|
|
36
|
+
const bytes = crypto.getRandomValues(new Uint8Array(size));
|
|
37
|
+
for (const byte of bytes) id += chars[byte % chars.length];
|
|
38
|
+
return id;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function groups<T>(values: T[], size: number): T[][] {
|
|
42
|
+
const result: T[][] = [];
|
|
43
|
+
for (let index = 0; index < values.length; index += size) {
|
|
44
|
+
result.push(values.slice(index, index + size));
|
|
45
|
+
}
|
|
46
|
+
return result;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export async function lockPrimaryBlocksFields(
|
|
50
|
+
db: ContentDb,
|
|
51
|
+
documentId: string,
|
|
52
|
+
): Promise<PrimaryBlocksField[]> {
|
|
53
|
+
return (
|
|
54
|
+
(await lockPrimaryBlocksFieldsForDocuments(db, [documentId])).get(
|
|
55
|
+
documentId,
|
|
56
|
+
) ?? []
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export async function lockPrimaryBlocksFieldsForDocuments(
|
|
61
|
+
db: ContentDb,
|
|
62
|
+
documentIds: string[],
|
|
63
|
+
): Promise<Map<string, PrimaryBlocksField[]>> {
|
|
64
|
+
const uniqueDocumentIds = [...new Set(documentIds)];
|
|
65
|
+
const memberships: Array<{
|
|
66
|
+
id: string;
|
|
67
|
+
documentId: string;
|
|
68
|
+
}> = [];
|
|
69
|
+
for (const documentIdGroup of groups(uniqueDocumentIds, 90)) {
|
|
70
|
+
memberships.push(
|
|
71
|
+
...(await db
|
|
72
|
+
.select({
|
|
73
|
+
id: schema.contentDatabaseItems.id,
|
|
74
|
+
documentId: schema.contentDatabaseItems.documentId,
|
|
75
|
+
})
|
|
76
|
+
.from(schema.contentDatabaseItems)
|
|
77
|
+
.where(
|
|
78
|
+
inArray(schema.contentDatabaseItems.documentId, documentIdGroup),
|
|
79
|
+
)),
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
const membershipIds = memberships.map((membership) => membership.id);
|
|
83
|
+
await lockDatabaseMemberships(db, membershipIds);
|
|
84
|
+
if (membershipIds.length === 0) return new Map();
|
|
85
|
+
|
|
86
|
+
const fields: Array<{
|
|
87
|
+
documentId: string;
|
|
88
|
+
propertyId: string | null;
|
|
89
|
+
ownerEmail: string;
|
|
90
|
+
}> = [];
|
|
91
|
+
for (const membershipIdGroup of groups(membershipIds, 90)) {
|
|
92
|
+
fields.push(
|
|
93
|
+
...(await db
|
|
94
|
+
.select({
|
|
95
|
+
documentId: schema.contentDatabaseItems.documentId,
|
|
96
|
+
propertyId: schema.contentDatabases.primaryBlocksPropertyId,
|
|
97
|
+
ownerEmail: schema.contentDatabases.ownerEmail,
|
|
98
|
+
})
|
|
99
|
+
.from(schema.contentDatabaseItems)
|
|
100
|
+
.innerJoin(
|
|
101
|
+
schema.contentDatabases,
|
|
102
|
+
eq(
|
|
103
|
+
schema.contentDatabases.id,
|
|
104
|
+
schema.contentDatabaseItems.databaseId,
|
|
105
|
+
),
|
|
106
|
+
)
|
|
107
|
+
.where(inArray(schema.contentDatabaseItems.id, membershipIdGroup))),
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
const fieldsByDocument = new Map<string, Map<string, string>>();
|
|
111
|
+
for (const field of fields) {
|
|
112
|
+
if (!field.propertyId) continue;
|
|
113
|
+
const documentFields =
|
|
114
|
+
fieldsByDocument.get(field.documentId) ?? new Map<string, string>();
|
|
115
|
+
documentFields.set(field.propertyId, field.ownerEmail);
|
|
116
|
+
fieldsByDocument.set(field.documentId, documentFields);
|
|
117
|
+
}
|
|
118
|
+
return new Map(
|
|
119
|
+
[...fieldsByDocument].map(([id, documentFields]) => [
|
|
120
|
+
id,
|
|
121
|
+
[...documentFields].map(([propertyId, ownerEmail]) => ({
|
|
122
|
+
propertyId,
|
|
123
|
+
ownerEmail,
|
|
124
|
+
})),
|
|
125
|
+
]),
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
async function loadStoredIdentity(
|
|
130
|
+
db: ContentDb,
|
|
131
|
+
fieldId: string,
|
|
132
|
+
): Promise<StoredBlocksFieldIdentity | null> {
|
|
133
|
+
const [field] = await db
|
|
134
|
+
.select()
|
|
135
|
+
.from(schema.documentBlockFields)
|
|
136
|
+
.where(eq(schema.documentBlockFields.id, fieldId));
|
|
137
|
+
if (!field) return null;
|
|
138
|
+
const blocks = await db
|
|
139
|
+
.select()
|
|
140
|
+
.from(schema.documentBlocks)
|
|
141
|
+
.where(eq(schema.documentBlocks.fieldId, fieldId))
|
|
142
|
+
.orderBy(asc(schema.documentBlocks.sortIndex));
|
|
143
|
+
return {
|
|
144
|
+
fieldId,
|
|
145
|
+
revision: field.revision,
|
|
146
|
+
contentHash: field.contentHash,
|
|
147
|
+
blocks: blocks.map((block) => ({
|
|
148
|
+
id: block.id,
|
|
149
|
+
parentId: block.parentId,
|
|
150
|
+
kind: block.kind,
|
|
151
|
+
position: block.position,
|
|
152
|
+
addressable: block.addressable,
|
|
153
|
+
contentHash: block.contentHash,
|
|
154
|
+
markdown: block.markdown,
|
|
155
|
+
state: block.state === "deleted" ? "deleted" : "live",
|
|
156
|
+
deletedAtRevision: block.deletedAtRevision,
|
|
157
|
+
recoveredAtRevision: block.recoveredAtRevision,
|
|
158
|
+
})),
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export async function readBlocksFieldIdentity(args: {
|
|
163
|
+
db?: ContentDb;
|
|
164
|
+
documentId: string;
|
|
165
|
+
propertyId: string;
|
|
166
|
+
markdown: string;
|
|
167
|
+
}): Promise<BlocksFieldIdentity> {
|
|
168
|
+
const fieldId = blocksFieldId(args.documentId, args.propertyId);
|
|
169
|
+
const stored = await loadStoredIdentity(args.db ?? getDb(), fieldId);
|
|
170
|
+
return stored
|
|
171
|
+
? exposeBlocksFieldIdentity(stored, args.markdown)
|
|
172
|
+
: legacyBlocksFieldIdentity(args);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export async function readBlocksFieldIdentities(args: {
|
|
176
|
+
db?: ContentDb;
|
|
177
|
+
fields: Array<{
|
|
178
|
+
documentId: string;
|
|
179
|
+
propertyId: string;
|
|
180
|
+
markdown: string;
|
|
181
|
+
}>;
|
|
182
|
+
}): Promise<Map<string, BlocksFieldIdentity>> {
|
|
183
|
+
const db = args.db ?? getDb();
|
|
184
|
+
const inputs = new Map(
|
|
185
|
+
args.fields.map((field) => [
|
|
186
|
+
blocksFieldId(field.documentId, field.propertyId),
|
|
187
|
+
field,
|
|
188
|
+
]),
|
|
189
|
+
);
|
|
190
|
+
const storedFields: Array<typeof schema.documentBlockFields.$inferSelect> =
|
|
191
|
+
[];
|
|
192
|
+
for (const ids of groups([...inputs.keys()], 200)) {
|
|
193
|
+
if (ids.length === 0) continue;
|
|
194
|
+
storedFields.push(
|
|
195
|
+
...(await db
|
|
196
|
+
.select()
|
|
197
|
+
.from(schema.documentBlockFields)
|
|
198
|
+
.where(inArray(schema.documentBlockFields.id, ids))),
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
const storedById = new Map(storedFields.map((field) => [field.id, field]));
|
|
202
|
+
const storedBlocks: Array<typeof schema.documentBlocks.$inferSelect> = [];
|
|
203
|
+
for (const ids of groups(
|
|
204
|
+
storedFields.map((field) => field.id),
|
|
205
|
+
200,
|
|
206
|
+
)) {
|
|
207
|
+
if (ids.length === 0) continue;
|
|
208
|
+
storedBlocks.push(
|
|
209
|
+
...(await db
|
|
210
|
+
.select()
|
|
211
|
+
.from(schema.documentBlocks)
|
|
212
|
+
.where(inArray(schema.documentBlocks.fieldId, ids))
|
|
213
|
+
.orderBy(asc(schema.documentBlocks.sortIndex))),
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
const blocksByField = new Map<
|
|
217
|
+
string,
|
|
218
|
+
Array<typeof schema.documentBlocks.$inferSelect>
|
|
219
|
+
>();
|
|
220
|
+
for (const block of storedBlocks) {
|
|
221
|
+
const values = blocksByField.get(block.fieldId) ?? [];
|
|
222
|
+
values.push(block);
|
|
223
|
+
blocksByField.set(block.fieldId, values);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const result = new Map<string, BlocksFieldIdentity>();
|
|
227
|
+
for (const [fieldId, input] of inputs) {
|
|
228
|
+
const field = storedById.get(fieldId);
|
|
229
|
+
if (!field) {
|
|
230
|
+
result.set(fieldId, legacyBlocksFieldIdentity(input));
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
233
|
+
result.set(
|
|
234
|
+
fieldId,
|
|
235
|
+
exposeBlocksFieldIdentity(
|
|
236
|
+
{
|
|
237
|
+
fieldId,
|
|
238
|
+
revision: field.revision,
|
|
239
|
+
contentHash: field.contentHash,
|
|
240
|
+
blocks: (blocksByField.get(fieldId) ?? []).map((block) => ({
|
|
241
|
+
id: block.id,
|
|
242
|
+
parentId: block.parentId,
|
|
243
|
+
kind: block.kind,
|
|
244
|
+
position: block.position,
|
|
245
|
+
addressable: block.addressable,
|
|
246
|
+
contentHash: block.contentHash,
|
|
247
|
+
markdown: block.markdown,
|
|
248
|
+
state: block.state === "deleted" ? "deleted" : "live",
|
|
249
|
+
deletedAtRevision: block.deletedAtRevision,
|
|
250
|
+
recoveredAtRevision: block.recoveredAtRevision,
|
|
251
|
+
})),
|
|
252
|
+
},
|
|
253
|
+
input.markdown,
|
|
254
|
+
),
|
|
255
|
+
);
|
|
256
|
+
}
|
|
257
|
+
return result;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
export async function persistBlocksFieldIdentity(args: {
|
|
261
|
+
db: ContentDb;
|
|
262
|
+
ownerEmail: string;
|
|
263
|
+
documentId: string;
|
|
264
|
+
propertyId: string;
|
|
265
|
+
previousMarkdown: string;
|
|
266
|
+
markdown: string;
|
|
267
|
+
expectedRevision?: number;
|
|
268
|
+
now: string;
|
|
269
|
+
}): Promise<StoredBlocksFieldIdentity> {
|
|
270
|
+
const fieldId = blocksFieldId(args.documentId, args.propertyId);
|
|
271
|
+
const stored = await loadStoredIdentity(args.db, fieldId);
|
|
272
|
+
const actualRevision = stored?.revision ?? 0;
|
|
273
|
+
if (
|
|
274
|
+
args.expectedRevision !== undefined &&
|
|
275
|
+
args.expectedRevision !== actualRevision
|
|
276
|
+
) {
|
|
277
|
+
throw new BlocksFieldRevisionConflictError(
|
|
278
|
+
`Blocks field revision conflict: expected ${args.expectedRevision}, current ${actualRevision}`,
|
|
279
|
+
);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
let previous =
|
|
283
|
+
stored ??
|
|
284
|
+
materializeLegacyBlocksFieldIdentity({
|
|
285
|
+
documentId: args.documentId,
|
|
286
|
+
propertyId: args.propertyId,
|
|
287
|
+
markdown: args.previousMarkdown,
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
// A legacy whole-field writer may have changed Markdown without updating the
|
|
291
|
+
// sidecar. Reconcile exact unique blocks first and report the resulting extra
|
|
292
|
+
// revision instead of silently pretending continuity was complete.
|
|
293
|
+
if (previous.contentHash !== blocksContentHash(args.previousMarkdown)) {
|
|
294
|
+
previous = reconcileBlocksFieldIdentity({
|
|
295
|
+
documentId: args.documentId,
|
|
296
|
+
propertyId: args.propertyId,
|
|
297
|
+
previous,
|
|
298
|
+
markdown: args.previousMarkdown,
|
|
299
|
+
createId: () => `block_${nanoid(16)}`,
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
let next = reconcileBlocksFieldIdentity({
|
|
304
|
+
documentId: args.documentId,
|
|
305
|
+
propertyId: args.propertyId,
|
|
306
|
+
previous,
|
|
307
|
+
markdown: args.markdown,
|
|
308
|
+
createId: () => `block_${nanoid(16)}`,
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
if (next.blocks.length > 0) {
|
|
312
|
+
const existingOwners = await args.db
|
|
313
|
+
.select({
|
|
314
|
+
id: schema.documentBlocks.id,
|
|
315
|
+
fieldId: schema.documentBlocks.fieldId,
|
|
316
|
+
})
|
|
317
|
+
.from(schema.documentBlocks)
|
|
318
|
+
.where(
|
|
319
|
+
inArray(
|
|
320
|
+
schema.documentBlocks.id,
|
|
321
|
+
next.blocks.map((block) => block.id),
|
|
322
|
+
),
|
|
323
|
+
);
|
|
324
|
+
const remappedIds = new Map<string, string>();
|
|
325
|
+
const reserved = new Set(next.blocks.map((block) => block.id));
|
|
326
|
+
for (const existing of existingOwners) {
|
|
327
|
+
if (existing.fieldId === fieldId) continue;
|
|
328
|
+
let replacement = `block_${nanoid(16)}`;
|
|
329
|
+
while (reserved.has(replacement)) replacement = `block_${nanoid(16)}`;
|
|
330
|
+
reserved.add(replacement);
|
|
331
|
+
remappedIds.set(existing.id, replacement);
|
|
332
|
+
}
|
|
333
|
+
if (remappedIds.size > 0) {
|
|
334
|
+
next = {
|
|
335
|
+
...next,
|
|
336
|
+
blocks: next.blocks.map((block) => ({
|
|
337
|
+
...block,
|
|
338
|
+
id: remappedIds.get(block.id) ?? block.id,
|
|
339
|
+
parentId: block.parentId
|
|
340
|
+
? (remappedIds.get(block.parentId) ?? block.parentId)
|
|
341
|
+
: null,
|
|
342
|
+
})),
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
if (stored) {
|
|
348
|
+
const applied = await args.db
|
|
349
|
+
.update(schema.documentBlockFields)
|
|
350
|
+
.set({
|
|
351
|
+
revision: next.revision,
|
|
352
|
+
contentHash: next.contentHash,
|
|
353
|
+
updatedAt: args.now,
|
|
354
|
+
})
|
|
355
|
+
.where(
|
|
356
|
+
and(
|
|
357
|
+
eq(schema.documentBlockFields.id, fieldId),
|
|
358
|
+
eq(schema.documentBlockFields.revision, actualRevision),
|
|
359
|
+
),
|
|
360
|
+
)
|
|
361
|
+
.returning({ id: schema.documentBlockFields.id });
|
|
362
|
+
if (applied.length === 0) {
|
|
363
|
+
throw new BlocksFieldRevisionConflictError(
|
|
364
|
+
`Blocks field revision conflict: expected ${actualRevision}, current revision changed`,
|
|
365
|
+
);
|
|
366
|
+
}
|
|
367
|
+
} else {
|
|
368
|
+
const inserted = await args.db
|
|
369
|
+
.insert(schema.documentBlockFields)
|
|
370
|
+
.values({
|
|
371
|
+
id: fieldId,
|
|
372
|
+
ownerEmail: args.ownerEmail,
|
|
373
|
+
documentId: args.documentId,
|
|
374
|
+
propertyId: args.propertyId,
|
|
375
|
+
revision: next.revision,
|
|
376
|
+
contentHash: next.contentHash,
|
|
377
|
+
createdAt: args.now,
|
|
378
|
+
updatedAt: args.now,
|
|
379
|
+
})
|
|
380
|
+
.onConflictDoNothing()
|
|
381
|
+
.returning({ id: schema.documentBlockFields.id });
|
|
382
|
+
if (inserted.length === 0) {
|
|
383
|
+
throw new BlocksFieldRevisionConflictError(
|
|
384
|
+
"Blocks field revision conflict: concurrent first materialization",
|
|
385
|
+
);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
await args.db
|
|
389
|
+
.delete(schema.documentBlocks)
|
|
390
|
+
.where(eq(schema.documentBlocks.fieldId, fieldId));
|
|
391
|
+
if (next.blocks.length > 0) {
|
|
392
|
+
await args.db.insert(schema.documentBlocks).values(
|
|
393
|
+
next.blocks.map((block, sortIndex) => ({
|
|
394
|
+
id: block.id,
|
|
395
|
+
ownerEmail: args.ownerEmail,
|
|
396
|
+
fieldId,
|
|
397
|
+
parentId: block.parentId,
|
|
398
|
+
kind: block.kind,
|
|
399
|
+
position: block.position,
|
|
400
|
+
sortIndex,
|
|
401
|
+
addressable: block.addressable,
|
|
402
|
+
contentHash: block.contentHash,
|
|
403
|
+
markdown: block.markdown,
|
|
404
|
+
state: block.state,
|
|
405
|
+
deletedAtRevision: block.deletedAtRevision,
|
|
406
|
+
recoveredAtRevision: block.recoveredAtRevision,
|
|
407
|
+
createdAt: args.now,
|
|
408
|
+
updatedAt: args.now,
|
|
409
|
+
})),
|
|
410
|
+
);
|
|
411
|
+
}
|
|
412
|
+
return next;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
export async function deleteBlocksFieldIdentity(args: {
|
|
416
|
+
db: ContentDb;
|
|
417
|
+
documentId?: string;
|
|
418
|
+
documentIds?: string[];
|
|
419
|
+
propertyId?: string;
|
|
420
|
+
propertyIds?: string[];
|
|
421
|
+
}): Promise<void> {
|
|
422
|
+
if (
|
|
423
|
+
!args.documentId &&
|
|
424
|
+
!args.documentIds?.length &&
|
|
425
|
+
!args.propertyId &&
|
|
426
|
+
!args.propertyIds?.length
|
|
427
|
+
) {
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
const clauses = [];
|
|
431
|
+
if (args.documentId) {
|
|
432
|
+
clauses.push(eq(schema.documentBlockFields.documentId, args.documentId));
|
|
433
|
+
}
|
|
434
|
+
if (args.documentIds?.length) {
|
|
435
|
+
clauses.push(
|
|
436
|
+
inArray(schema.documentBlockFields.documentId, args.documentIds),
|
|
437
|
+
);
|
|
438
|
+
}
|
|
439
|
+
if (args.propertyId) {
|
|
440
|
+
clauses.push(eq(schema.documentBlockFields.propertyId, args.propertyId));
|
|
441
|
+
}
|
|
442
|
+
if (args.propertyIds?.length) {
|
|
443
|
+
clauses.push(
|
|
444
|
+
inArray(schema.documentBlockFields.propertyId, args.propertyIds),
|
|
445
|
+
);
|
|
446
|
+
}
|
|
447
|
+
const fields = await args.db
|
|
448
|
+
.select({ id: schema.documentBlockFields.id })
|
|
449
|
+
.from(schema.documentBlockFields)
|
|
450
|
+
.where(clauses.length === 1 ? clauses[0] : and(...clauses));
|
|
451
|
+
if (fields.length === 0) return;
|
|
452
|
+
await args.db.delete(schema.documentBlocks).where(
|
|
453
|
+
inArray(
|
|
454
|
+
schema.documentBlocks.fieldId,
|
|
455
|
+
fields.map((field) => field.id),
|
|
456
|
+
),
|
|
457
|
+
);
|
|
458
|
+
await args.db.delete(schema.documentBlockFields).where(
|
|
459
|
+
inArray(
|
|
460
|
+
schema.documentBlockFields.id,
|
|
461
|
+
fields.map((field) => field.id),
|
|
462
|
+
),
|
|
463
|
+
);
|
|
464
|
+
}
|
|
@@ -38,6 +38,7 @@ 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,
|
|
@@ -1417,15 +1418,21 @@ export async function deleteDatabaseDataForDocument(
|
|
|
1417
1418
|
.from(schema.documentPropertyDefinitions)
|
|
1418
1419
|
.where(eq(schema.documentPropertyDefinitions.databaseId, database.id));
|
|
1419
1420
|
|
|
1420
|
-
|
|
1421
|
+
const definitionIds = definitions.map((definition) => definition.id);
|
|
1422
|
+
if (definitionIds.length > 0) {
|
|
1423
|
+
await deleteBlocksFieldIdentity({ db, propertyIds: definitionIds });
|
|
1421
1424
|
await db
|
|
1422
1425
|
.delete(schema.documentPropertyValues)
|
|
1423
|
-
.where(
|
|
1426
|
+
.where(
|
|
1427
|
+
inArray(schema.documentPropertyValues.propertyId, definitionIds),
|
|
1428
|
+
);
|
|
1424
1429
|
// Independent Blocks-field content is keyed by property id; drop it so
|
|
1425
1430
|
// deleting a database leaves no orphaned document_block_field_contents.
|
|
1426
1431
|
await db
|
|
1427
1432
|
.delete(schema.documentBlockFieldContents)
|
|
1428
|
-
.where(
|
|
1433
|
+
.where(
|
|
1434
|
+
inArray(schema.documentBlockFieldContents.propertyId, definitionIds),
|
|
1435
|
+
);
|
|
1429
1436
|
}
|
|
1430
1437
|
const sources = await db
|
|
1431
1438
|
.select({ id: schema.contentDatabaseSources.id })
|
|
@@ -1482,6 +1489,7 @@ export async function deleteDatabaseDataForDocument(
|
|
|
1482
1489
|
db,
|
|
1483
1490
|
);
|
|
1484
1491
|
if (item) {
|
|
1492
|
+
await deleteBlocksFieldIdentity({ db, documentId });
|
|
1485
1493
|
await db
|
|
1486
1494
|
.delete(schema.contentDatabaseItemKeyClaims)
|
|
1487
1495
|
.where(eq(schema.contentDatabaseItemKeyClaims.documentId, documentId));
|