@agent-native/core 0.134.0 → 0.134.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/template-content-developers.mdx +1 -1
- package/corpus/core/package.json +1 -1
- package/corpus/templates/clips/changelog/2026-07-29-meeting-notes-no-longer-transcribe-the-other-side-twice-when.md +6 -0
- package/corpus/templates/clips/desktop/src/hooks/useMeetingTranscription.ts +37 -48
- package/corpus/templates/clips/desktop/src/lib/transcription-capture.ts +7 -5
- package/corpus/templates/clips/desktop/src/lib/transcription-engine.ts +182 -106
- package/corpus/templates/clips/desktop/src/overlays/live-transcript.tsx +43 -14
- package/corpus/templates/clips/desktop/src/overlays/recording-pill.tsx +3 -3
- package/corpus/templates/clips/desktop/src-tauri/src/echo_guard.rs +443 -0
- package/corpus/templates/clips/desktop/src-tauri/src/lib.rs +1 -0
- package/corpus/templates/clips/desktop/src-tauri/src/whisper_speech.rs +125 -16
- package/corpus/templates/content/.agents/skills/document-editing/references/databases.md +6 -4
- package/corpus/templates/content/actions/_database-membership-lock.ts +25 -0
- package/corpus/templates/content/actions/_database-row-batch.ts +6 -2
- package/corpus/templates/content/actions/_database-source-utils.ts +289 -129
- package/corpus/templates/content/actions/_database-utils.ts +12 -3
- package/corpus/templates/content/actions/add-content-database-source-field-property.ts +39 -21
- package/corpus/templates/content/actions/attach-content-database-source.ts +4 -1
- package/corpus/templates/content/actions/change-content-database-source-role.ts +3 -5
- package/corpus/templates/content/actions/duplicate-document-property.ts +59 -46
- package/corpus/templates/content/actions/execute-builder-source-execution.ts +77 -71
- package/corpus/templates/content/actions/materialize-builder-required-fields.ts +7 -0
- package/corpus/templates/content/actions/remove-database-items.ts +183 -0
- package/corpus/templates/content/actions/set-document-property.ts +57 -39
- package/corpus/templates/content/actions/sync-local-folder-source.ts +7 -0
- package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +471 -329
- package/corpus/templates/content/app/components/editor/database/row-access.ts +45 -0
- package/corpus/templates/content/app/components/editor/database/shared.tsx +141 -38
- package/corpus/templates/content/app/hooks/use-content-database.ts +2 -2
- package/corpus/templates/content/app/i18n-data.ts +130 -0
- package/corpus/templates/content/changelog/2026-07-30-database-rows-can-now-be-removed-without-deleting-their-page.md +6 -0
- package/corpus/templates/content/parity/eval-scenarios.ts +2 -2
- package/corpus/templates/content/parity/matrix.md +1 -1
- package/corpus/templates/content/parity/matrix.ts +4 -5
- package/corpus/templates/content/shared/api.ts +4 -0
- package/corpus/templates/design/.generated/bridge/editor-chrome.generated.ts +26 -11
- package/corpus/templates/design/actions/take-design-screenshot.ts +7 -0
- package/corpus/templates/design/app/components/design/DesignCanvas.tsx +11 -3
- package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +4 -1
- package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +54 -17
- package/corpus/templates/design/app/components/design/design-canvas/local-runtime.ts +126 -0
- package/corpus/templates/design/app/components/templates/TemplatePreview.tsx +2 -0
- package/corpus/templates/design/app/pages/Index.tsx +3 -1
- package/corpus/templates/design/app/pages/Present.tsx +2 -1
- package/corpus/templates/design/changelog/2026-07-30-a-broken-inline-script-in-a-generated-screen-is-now-caught-o.md +6 -0
- package/corpus/templates/design/changelog/2026-07-30-design-html-is-now-validated-with-a-spec-grade-html-parser-i.md +6 -0
- package/corpus/templates/design/changelog/2026-07-30-designs-now-load-their-tailwind-and-alpine-runtimes-from-the.md +6 -0
- package/corpus/templates/design/changelog/2026-07-30-designs-with-a-broken-alpine-expression-are-now-caught-on-sa.md +6 -0
- package/corpus/templates/design/changelog/2026-07-30-editing-a-design-no-longer-strips-its-styling-the-canvas-kep.md +6 -0
- package/corpus/templates/design/package.json +4 -0
- package/corpus/templates/design/shared/html-integrity.ts +802 -556
- package/dist/notifications/routes.d.ts +3 -3
- package/docs/content/template-content-developers.mdx +1 -1
- package/package.json +1 -1
- package/corpus/templates/content/actions/delete-database-items.ts +0 -89
|
@@ -28,6 +28,7 @@ import {
|
|
|
28
28
|
builderCmsQualifiedId,
|
|
29
29
|
type BuilderCmsSourceEntry,
|
|
30
30
|
} from "./_builder-cms-source-adapter.js";
|
|
31
|
+
import { lockDatabaseMemberships } from "./_database-membership-lock.js";
|
|
31
32
|
import {
|
|
32
33
|
resolveDatabaseForSourceMutation,
|
|
33
34
|
serializeSourceField,
|
|
@@ -460,6 +461,11 @@ export default defineAction({
|
|
|
460
461
|
documentId: string;
|
|
461
462
|
sourceValuesJson: string | null;
|
|
462
463
|
}> = [];
|
|
464
|
+
const sourceRowUpdates: Array<{
|
|
465
|
+
id: string;
|
|
466
|
+
previousSourceValuesJson: string;
|
|
467
|
+
sourceValuesJson: string;
|
|
468
|
+
}> = [];
|
|
463
469
|
if (builderEntries) {
|
|
464
470
|
const currentSourceRows = await tx
|
|
465
471
|
.select()
|
|
@@ -487,27 +493,11 @@ export default defineAction({
|
|
|
487
493
|
) {
|
|
488
494
|
continue;
|
|
489
495
|
}
|
|
490
|
-
|
|
491
|
-
.
|
|
492
|
-
.
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
})
|
|
496
|
-
.where(
|
|
497
|
-
and(
|
|
498
|
-
eq(schema.contentDatabaseSourceRows.id, currentRow.id),
|
|
499
|
-
eq(
|
|
500
|
-
schema.contentDatabaseSourceRows.sourceValuesJson,
|
|
501
|
-
currentRow.sourceValuesJson,
|
|
502
|
-
),
|
|
503
|
-
),
|
|
504
|
-
)
|
|
505
|
-
.returning({ id: schema.contentDatabaseSourceRows.id });
|
|
506
|
-
if (!updatedRow) {
|
|
507
|
-
throw new Error(
|
|
508
|
-
"The Builder source changed while adding this property. No property was created; try again.",
|
|
509
|
-
);
|
|
510
|
-
}
|
|
496
|
+
sourceRowUpdates.push({
|
|
497
|
+
id: currentRow.id,
|
|
498
|
+
previousSourceValuesJson: currentRow.sourceValuesJson,
|
|
499
|
+
sourceValuesJson: mergedRow.sourceValuesJson,
|
|
500
|
+
});
|
|
511
501
|
}
|
|
512
502
|
sourceRows = merged.rows;
|
|
513
503
|
} else if (!isSecondary) {
|
|
@@ -545,6 +535,34 @@ export default defineAction({
|
|
|
545
535
|
);
|
|
546
536
|
}
|
|
547
537
|
|
|
538
|
+
await lockDatabaseMemberships(
|
|
539
|
+
tx,
|
|
540
|
+
sourceRows.map((row) => row.databaseItemId),
|
|
541
|
+
);
|
|
542
|
+
for (const update of sourceRowUpdates) {
|
|
543
|
+
const [updatedRow] = await tx
|
|
544
|
+
.update(schema.contentDatabaseSourceRows)
|
|
545
|
+
.set({
|
|
546
|
+
sourceValuesJson: update.sourceValuesJson,
|
|
547
|
+
updatedAt: now,
|
|
548
|
+
})
|
|
549
|
+
.where(
|
|
550
|
+
and(
|
|
551
|
+
eq(schema.contentDatabaseSourceRows.id, update.id),
|
|
552
|
+
eq(
|
|
553
|
+
schema.contentDatabaseSourceRows.sourceValuesJson,
|
|
554
|
+
update.previousSourceValuesJson,
|
|
555
|
+
),
|
|
556
|
+
),
|
|
557
|
+
)
|
|
558
|
+
.returning({ id: schema.contentDatabaseSourceRows.id });
|
|
559
|
+
if (!updatedRow) {
|
|
560
|
+
throw new Error(
|
|
561
|
+
"The Builder source changed while adding this property. No property was created; try again.",
|
|
562
|
+
);
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
|
|
548
566
|
const builderMetadata = builderMetadataForSourceField({
|
|
549
567
|
sourceFieldKey: currentField.sourceFieldKey,
|
|
550
568
|
sourceMetadataJson: currentSource.metadataJson,
|
|
@@ -27,6 +27,7 @@ import {
|
|
|
27
27
|
importBuilderCmsEntriesAsDatabaseItems,
|
|
28
28
|
insertSecondarySource,
|
|
29
29
|
mapBuilderCmsEntriesToLocalItems,
|
|
30
|
+
replaceMockSourceRows,
|
|
30
31
|
replaceSourceMetadata,
|
|
31
32
|
resolveDatabaseForSourceMutation,
|
|
32
33
|
seedMockSourceFields,
|
|
@@ -511,6 +512,7 @@ export default defineAction({
|
|
|
511
512
|
if (additionalRead.state === "live") {
|
|
512
513
|
const importResult = await importBuilderCmsEntriesAsDatabaseItems({
|
|
513
514
|
database,
|
|
515
|
+
sourceId: additionalSourceId,
|
|
514
516
|
entries: additionalEntries,
|
|
515
517
|
now,
|
|
516
518
|
sourceTable,
|
|
@@ -634,6 +636,7 @@ export default defineAction({
|
|
|
634
636
|
if (builderRead?.state === "live") {
|
|
635
637
|
const importResult = await importBuilderCmsEntriesAsDatabaseItems({
|
|
636
638
|
database,
|
|
639
|
+
sourceId,
|
|
637
640
|
entries: builderEntries,
|
|
638
641
|
now,
|
|
639
642
|
sourceTable,
|
|
@@ -682,7 +685,7 @@ export default defineAction({
|
|
|
682
685
|
builderSampleEntries: builderEntries,
|
|
683
686
|
now,
|
|
684
687
|
});
|
|
685
|
-
await
|
|
688
|
+
await replaceMockSourceRows({
|
|
686
689
|
sourceId,
|
|
687
690
|
ownerEmail: database.ownerEmail,
|
|
688
691
|
sourceType,
|
|
@@ -23,9 +23,9 @@ import {
|
|
|
23
23
|
importBuilderCmsEntriesAsDatabaseItems,
|
|
24
24
|
mapBuilderCmsEntriesToLocalItems,
|
|
25
25
|
mutateContentDatabaseSourceMetadata,
|
|
26
|
+
replaceMockSourceRows,
|
|
26
27
|
resolveDatabaseForSourceMutation,
|
|
27
28
|
seedMockSourceFields,
|
|
28
|
-
seedMockSourceRows,
|
|
29
29
|
seedSecondarySourceFields,
|
|
30
30
|
sourceSetupPayload,
|
|
31
31
|
storeSecondarySourceRows,
|
|
@@ -400,9 +400,6 @@ export default defineAction({
|
|
|
400
400
|
await db
|
|
401
401
|
.delete(schema.contentDatabaseSourceFields)
|
|
402
402
|
.where(eq(schema.contentDatabaseSourceFields.sourceId, source.id));
|
|
403
|
-
await db
|
|
404
|
-
.delete(schema.contentDatabaseSourceRows)
|
|
405
|
-
.where(eq(schema.contentDatabaseSourceRows.sourceId, source.id));
|
|
406
403
|
await clearSourceFederation(source.id, now);
|
|
407
404
|
|
|
408
405
|
let importedEntriesByDocumentId = new Map<
|
|
@@ -412,6 +409,7 @@ export default defineAction({
|
|
|
412
409
|
if (read.state === "live") {
|
|
413
410
|
const importResult = await importBuilderCmsEntriesAsDatabaseItems({
|
|
414
411
|
database,
|
|
412
|
+
sourceId: source.id,
|
|
415
413
|
entries,
|
|
416
414
|
now,
|
|
417
415
|
sourceTable: source.sourceTable,
|
|
@@ -443,7 +441,7 @@ export default defineAction({
|
|
|
443
441
|
builderSampleEntries: entries,
|
|
444
442
|
now,
|
|
445
443
|
});
|
|
446
|
-
await
|
|
444
|
+
await replaceMockSourceRows({
|
|
447
445
|
sourceId: source.id,
|
|
448
446
|
ownerEmail: database.ownerEmail,
|
|
449
447
|
sourceType: "builder-cms",
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
serializePropertyOptions,
|
|
11
11
|
type DocumentPropertyType,
|
|
12
12
|
} from "../shared/properties.js";
|
|
13
|
+
import { lockDatabaseMemberships } from "./_database-membership-lock.js";
|
|
13
14
|
import {
|
|
14
15
|
propertyDefinitionsPositionScope,
|
|
15
16
|
withPositionLock,
|
|
@@ -76,57 +77,69 @@ export default defineAction({
|
|
|
76
77
|
await withPositionLock(
|
|
77
78
|
propertyDefinitionsPositionScope(database.id),
|
|
78
79
|
async () => {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
schema.documentPropertyDefinitions.ownerEmail,
|
|
88
|
-
document.ownerEmail,
|
|
89
|
-
),
|
|
90
|
-
eq(schema.documentPropertyDefinitions.databaseId, database.id),
|
|
91
|
-
),
|
|
80
|
+
await db.transaction(async (tx) => {
|
|
81
|
+
const memberships = await tx
|
|
82
|
+
.select({ id: schema.contentDatabaseItems.id })
|
|
83
|
+
.from(schema.contentDatabaseItems)
|
|
84
|
+
.where(eq(schema.contentDatabaseItems.databaseId, database.id));
|
|
85
|
+
await lockDatabaseMemberships(
|
|
86
|
+
tx,
|
|
87
|
+
memberships.map((membership) => membership.id),
|
|
92
88
|
);
|
|
93
89
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
);
|
|
90
|
+
const [maxPos] = await tx
|
|
91
|
+
.select({
|
|
92
|
+
max: sql<number>`COALESCE(MAX(position), -1)`,
|
|
93
|
+
})
|
|
94
|
+
.from(schema.documentPropertyDefinitions)
|
|
95
|
+
.where(
|
|
96
|
+
and(
|
|
97
|
+
eq(
|
|
98
|
+
schema.documentPropertyDefinitions.ownerEmail,
|
|
99
|
+
document.ownerEmail,
|
|
100
|
+
),
|
|
101
|
+
eq(schema.documentPropertyDefinitions.databaseId, database.id),
|
|
102
|
+
),
|
|
103
|
+
);
|
|
109
104
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
ownerEmail: value.ownerEmail,
|
|
121
|
-
documentId: value.documentId,
|
|
122
|
-
propertyId: newPropertyId,
|
|
123
|
-
valueJson: value.valueJson,
|
|
105
|
+
await tx.insert(schema.documentPropertyDefinitions).values({
|
|
106
|
+
id: newPropertyId,
|
|
107
|
+
ownerEmail: definition.ownerEmail,
|
|
108
|
+
orgId: definition.orgId,
|
|
109
|
+
databaseId: database.id,
|
|
110
|
+
name: `${definition.name} copy`,
|
|
111
|
+
type: definition.type,
|
|
112
|
+
visibility: definition.visibility,
|
|
113
|
+
optionsJson,
|
|
114
|
+
position: (maxPos?.max ?? -1) + 1,
|
|
124
115
|
createdAt: now,
|
|
125
116
|
updatedAt: now,
|
|
126
|
-
})
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
// Blocks fields don't use document_property_values; a duplicate
|
|
120
|
+
// starts empty.
|
|
121
|
+
if (!isBlocks) {
|
|
122
|
+
const values = await tx
|
|
123
|
+
.select()
|
|
124
|
+
.from(schema.documentPropertyValues)
|
|
125
|
+
.where(eq(schema.documentPropertyValues.propertyId, propertyId));
|
|
126
|
+
if (values.length > 0) {
|
|
127
|
+
await tx.insert(schema.documentPropertyValues).values(
|
|
128
|
+
values.map((value) => ({
|
|
129
|
+
id: nanoid(),
|
|
130
|
+
ownerEmail: value.ownerEmail,
|
|
131
|
+
documentId: value.documentId,
|
|
132
|
+
propertyId: newPropertyId,
|
|
133
|
+
valueJson: value.valueJson,
|
|
134
|
+
createdAt: now,
|
|
135
|
+
updatedAt: now,
|
|
136
|
+
})),
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
},
|
|
142
|
+
);
|
|
130
143
|
|
|
131
144
|
await writeAppState("refresh-signal", { ts: Date.now() });
|
|
132
145
|
|
|
@@ -44,6 +44,7 @@ import {
|
|
|
44
44
|
executeBuilderCmsWrite,
|
|
45
45
|
} from "./_builder-cms-write-client.js";
|
|
46
46
|
import { createBuilderSourceTiming } from "./_builder-source-timings.js";
|
|
47
|
+
import { lockDatabaseMemberships } from "./_database-membership-lock.js";
|
|
47
48
|
import {
|
|
48
49
|
getContentDatabaseSourceSnapshotForWrite,
|
|
49
50
|
resolveDatabaseForSourceMutation,
|
|
@@ -563,80 +564,85 @@ async function reconcileBuilderCmsWrite(args: {
|
|
|
563
564
|
}
|
|
564
565
|
|
|
565
566
|
const db = getDb();
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
567
|
+
await db.transaction(async (tx) => {
|
|
568
|
+
if (args.changeSet.databaseItemId) {
|
|
569
|
+
await lockDatabaseMemberships(tx, [args.changeSet.databaseItemId]);
|
|
570
|
+
}
|
|
571
|
+
const existingRow =
|
|
572
|
+
args.changeSet.documentId || args.changeSet.databaseItemId
|
|
573
|
+
? await tx
|
|
574
|
+
.select()
|
|
575
|
+
.from(schema.contentDatabaseSourceRows)
|
|
576
|
+
.where(
|
|
577
|
+
and(
|
|
578
|
+
eq(schema.contentDatabaseSourceRows.sourceId, args.source.id),
|
|
579
|
+
args.changeSet.documentId
|
|
580
|
+
? eq(
|
|
581
|
+
schema.contentDatabaseSourceRows.documentId,
|
|
582
|
+
args.changeSet.documentId,
|
|
583
|
+
)
|
|
584
|
+
: eq(
|
|
585
|
+
schema.contentDatabaseSourceRows.databaseItemId,
|
|
586
|
+
args.changeSet.databaseItemId as string,
|
|
587
|
+
),
|
|
588
|
+
),
|
|
589
|
+
)
|
|
590
|
+
.limit(1)
|
|
591
|
+
: [];
|
|
587
592
|
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
});
|
|
596
|
-
const patchWithValues = {
|
|
597
|
-
...patch,
|
|
598
|
-
sourceValuesJson,
|
|
599
|
-
};
|
|
600
|
-
if (row) {
|
|
601
|
-
await db
|
|
602
|
-
.update(schema.contentDatabaseSourceRows)
|
|
603
|
-
.set(patchWithValues)
|
|
604
|
-
.where(eq(schema.contentDatabaseSourceRows.id, row.id));
|
|
605
|
-
} else if (args.changeSet.documentId && args.changeSet.databaseItemId) {
|
|
606
|
-
await db.insert(schema.contentDatabaseSourceRows).values({
|
|
607
|
-
id: crypto.randomUUID(),
|
|
608
|
-
ownerEmail: args.database.ownerEmail,
|
|
609
|
-
sourceId: args.source.id,
|
|
610
|
-
databaseItemId: args.changeSet.databaseItemId,
|
|
611
|
-
documentId: args.changeSet.documentId,
|
|
612
|
-
createdAt: args.now,
|
|
613
|
-
...patchWithValues,
|
|
593
|
+
const [row] = existingRow;
|
|
594
|
+
const snapshotRow = sourceRowForChangeSet(args.source, args.changeSet);
|
|
595
|
+
const sourceValuesJson = builderCmsReconciledSourceValuesJson({
|
|
596
|
+
existingSourceValuesJson: row?.sourceValuesJson,
|
|
597
|
+
snapshotSourceValues: snapshotRow?.sourceValues,
|
|
598
|
+
changeSet: args.changeSet,
|
|
599
|
+
plan: args.plan,
|
|
614
600
|
});
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
601
|
+
const patchWithValues = {
|
|
602
|
+
...patch,
|
|
603
|
+
sourceValuesJson,
|
|
604
|
+
};
|
|
605
|
+
if (row) {
|
|
606
|
+
await tx
|
|
607
|
+
.update(schema.contentDatabaseSourceRows)
|
|
608
|
+
.set(patchWithValues)
|
|
609
|
+
.where(eq(schema.contentDatabaseSourceRows.id, row.id));
|
|
610
|
+
} else if (args.changeSet.documentId && args.changeSet.databaseItemId) {
|
|
611
|
+
await tx.insert(schema.contentDatabaseSourceRows).values({
|
|
612
|
+
id: crypto.randomUUID(),
|
|
613
|
+
ownerEmail: args.database.ownerEmail,
|
|
614
|
+
sourceId: args.source.id,
|
|
615
|
+
databaseItemId: args.changeSet.databaseItemId,
|
|
616
|
+
documentId: args.changeSet.documentId,
|
|
617
|
+
createdAt: args.now,
|
|
618
|
+
...patchWithValues,
|
|
619
|
+
});
|
|
620
|
+
} else {
|
|
621
|
+
throw new Error(
|
|
622
|
+
"Builder write succeeded, but the local source row was missing.",
|
|
623
|
+
);
|
|
624
|
+
}
|
|
620
625
|
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
626
|
+
await tx
|
|
627
|
+
.update(schema.contentDatabaseSourceFields)
|
|
628
|
+
.set({
|
|
629
|
+
freshness: "fresh",
|
|
630
|
+
lastSyncedAt: args.now,
|
|
631
|
+
updatedAt: args.now,
|
|
632
|
+
})
|
|
633
|
+
.where(eq(schema.contentDatabaseSourceFields.sourceId, args.source.id));
|
|
634
|
+
await tx
|
|
635
|
+
.update(schema.contentDatabaseSources)
|
|
636
|
+
.set({
|
|
637
|
+
syncState: "idle",
|
|
638
|
+
freshness: "fresh",
|
|
639
|
+
lastRefreshedAt: args.now,
|
|
640
|
+
lastSourceUpdatedAt: patch.lastSourceUpdatedAt,
|
|
641
|
+
lastError: null,
|
|
642
|
+
updatedAt: args.now,
|
|
643
|
+
})
|
|
644
|
+
.where(eq(schema.contentDatabaseSources.id, args.source.id));
|
|
645
|
+
});
|
|
640
646
|
}
|
|
641
647
|
|
|
642
648
|
export function realExecutionDeps(
|
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
} from "../shared/properties.js";
|
|
20
20
|
import { chunks } from "./_batch-utils.js";
|
|
21
21
|
import { readBuilderCmsContentEntries } from "./_builder-cms-read-client.js";
|
|
22
|
+
import { lockDatabaseMemberships } from "./_database-membership-lock.js";
|
|
22
23
|
import {
|
|
23
24
|
builderReferenceIdSourceValueKey,
|
|
24
25
|
resolveDatabaseForSourceMutation,
|
|
@@ -352,6 +353,12 @@ export default defineAction({
|
|
|
352
353
|
.select()
|
|
353
354
|
.from(schema.contentDatabaseSourceRows)
|
|
354
355
|
.where(eq(schema.contentDatabaseSourceRows.sourceId, source.id));
|
|
356
|
+
await lockDatabaseMemberships(
|
|
357
|
+
tx,
|
|
358
|
+
currentSourceRows
|
|
359
|
+
.map((row) => row.databaseItemId)
|
|
360
|
+
.filter((itemId) => itemId.length > 0),
|
|
361
|
+
);
|
|
355
362
|
const [maxPosition] = await tx
|
|
356
363
|
.select({ max: sql<number>`COALESCE(MAX(position), -1)` })
|
|
357
364
|
.from(schema.documentPropertyDefinitions)
|
|
@@ -0,0 +1,183 @@
|
|
|
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
|
+
import { and, eq, inArray } from "drizzle-orm";
|
|
5
|
+
|
|
6
|
+
import { getDb, schema } from "../server/db/index.js";
|
|
7
|
+
import { assertNotWorkspaceCatalogDocuments } from "./_content-space-catalog-guards.js";
|
|
8
|
+
import { lockDatabaseMemberships } from "./_database-membership-lock.js";
|
|
9
|
+
import {
|
|
10
|
+
databaseRowBatchSchema,
|
|
11
|
+
renumberDatabaseRows,
|
|
12
|
+
resolveDatabaseRowsForBatch,
|
|
13
|
+
} from "./_database-row-batch.js";
|
|
14
|
+
import { getContentDatabaseResponse } from "./_database-utils.js";
|
|
15
|
+
import {
|
|
16
|
+
databaseItemsPositionScope,
|
|
17
|
+
withPositionLock,
|
|
18
|
+
} from "./_position-utils.js";
|
|
19
|
+
|
|
20
|
+
export default defineAction({
|
|
21
|
+
description:
|
|
22
|
+
"Remove one or more page memberships from a content database in one atomic batch without deleting the pages. Use this once for two or more selected or named rows instead of looping page operations.",
|
|
23
|
+
schema: databaseRowBatchSchema,
|
|
24
|
+
run: async (args) => {
|
|
25
|
+
const db = getDb();
|
|
26
|
+
const { database, rows } = await resolveDatabaseRowsForBatch(args, {
|
|
27
|
+
includeTrashed: true,
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
if (database.systemRole === "favorites") {
|
|
31
|
+
await assertAccess("document", database.documentId, "editor");
|
|
32
|
+
const removedItemIds = rows.map((row) => row.item.id);
|
|
33
|
+
const removedDocumentIds = rows.map((row) => row.document.id);
|
|
34
|
+
const now = new Date().toISOString();
|
|
35
|
+
await withPositionLock(databaseItemsPositionScope(database.id), () =>
|
|
36
|
+
db.transaction(async (tx) => {
|
|
37
|
+
if (removedItemIds.length > 0) {
|
|
38
|
+
await tx
|
|
39
|
+
.delete(schema.contentDatabaseItems)
|
|
40
|
+
.where(inArray(schema.contentDatabaseItems.id, removedItemIds));
|
|
41
|
+
}
|
|
42
|
+
await renumberDatabaseRows(
|
|
43
|
+
tx as unknown as ReturnType<typeof getDb>,
|
|
44
|
+
database,
|
|
45
|
+
now,
|
|
46
|
+
);
|
|
47
|
+
}),
|
|
48
|
+
);
|
|
49
|
+
await writeAppState("refresh-signal", { ts: Date.now() });
|
|
50
|
+
return {
|
|
51
|
+
...(await getContentDatabaseResponse(database.id, {
|
|
52
|
+
limit: 100,
|
|
53
|
+
offset: 0,
|
|
54
|
+
})),
|
|
55
|
+
removedItemIds,
|
|
56
|
+
removedDocumentIds,
|
|
57
|
+
removedCount: removedItemIds.length,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
await assertAccess("document", database.documentId, "admin");
|
|
62
|
+
for (const row of rows) {
|
|
63
|
+
await assertAccess("document", row.document.id, "viewer");
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const removedItemIds = rows.map((row) => row.item.id);
|
|
67
|
+
const removedDocumentIds = rows.map((row) => row.document.id);
|
|
68
|
+
await assertNotWorkspaceCatalogDocuments(
|
|
69
|
+
db,
|
|
70
|
+
removedDocumentIds,
|
|
71
|
+
"removed from a database",
|
|
72
|
+
);
|
|
73
|
+
if (database.systemRole) {
|
|
74
|
+
throw new Error(
|
|
75
|
+
"System database memberships cannot be removed from this surface.",
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const now = new Date().toISOString();
|
|
80
|
+
|
|
81
|
+
await withPositionLock(databaseItemsPositionScope(database.id), () =>
|
|
82
|
+
db.transaction(async (tx) => {
|
|
83
|
+
if (removedItemIds.length > 0) {
|
|
84
|
+
await lockDatabaseMemberships(tx, removedItemIds);
|
|
85
|
+
const [sourceRow, hydrationRow] = await Promise.all([
|
|
86
|
+
tx
|
|
87
|
+
.select({ id: schema.contentDatabaseSourceRows.id })
|
|
88
|
+
.from(schema.contentDatabaseSourceRows)
|
|
89
|
+
.where(
|
|
90
|
+
inArray(
|
|
91
|
+
schema.contentDatabaseSourceRows.databaseItemId,
|
|
92
|
+
removedItemIds,
|
|
93
|
+
),
|
|
94
|
+
)
|
|
95
|
+
.limit(1),
|
|
96
|
+
tx
|
|
97
|
+
.select({ id: schema.contentDatabaseBodyHydrationQueue.id })
|
|
98
|
+
.from(schema.contentDatabaseBodyHydrationQueue)
|
|
99
|
+
.where(
|
|
100
|
+
inArray(
|
|
101
|
+
schema.contentDatabaseBodyHydrationQueue.databaseItemId,
|
|
102
|
+
removedItemIds,
|
|
103
|
+
),
|
|
104
|
+
)
|
|
105
|
+
.limit(1),
|
|
106
|
+
]);
|
|
107
|
+
if (sourceRow.length > 0 || hydrationRow.length > 0) {
|
|
108
|
+
throw new Error(
|
|
109
|
+
"Source-backed rows cannot be removed from this database.",
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
const propertyIds = (
|
|
114
|
+
await tx
|
|
115
|
+
.select({ id: schema.documentPropertyDefinitions.id })
|
|
116
|
+
.from(schema.documentPropertyDefinitions)
|
|
117
|
+
.where(
|
|
118
|
+
eq(schema.documentPropertyDefinitions.databaseId, database.id),
|
|
119
|
+
)
|
|
120
|
+
).map((property) => property.id);
|
|
121
|
+
if (removedDocumentIds.length > 0 && propertyIds.length > 0) {
|
|
122
|
+
await tx
|
|
123
|
+
.delete(schema.documentPropertyValues)
|
|
124
|
+
.where(
|
|
125
|
+
and(
|
|
126
|
+
inArray(
|
|
127
|
+
schema.documentPropertyValues.documentId,
|
|
128
|
+
removedDocumentIds,
|
|
129
|
+
),
|
|
130
|
+
inArray(schema.documentPropertyValues.propertyId, propertyIds),
|
|
131
|
+
),
|
|
132
|
+
);
|
|
133
|
+
await tx
|
|
134
|
+
.delete(schema.documentBlockFieldContents)
|
|
135
|
+
.where(
|
|
136
|
+
and(
|
|
137
|
+
inArray(
|
|
138
|
+
schema.documentBlockFieldContents.documentId,
|
|
139
|
+
removedDocumentIds,
|
|
140
|
+
),
|
|
141
|
+
inArray(
|
|
142
|
+
schema.documentBlockFieldContents.propertyId,
|
|
143
|
+
propertyIds,
|
|
144
|
+
),
|
|
145
|
+
),
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
if (removedItemIds.length > 0) {
|
|
149
|
+
await tx
|
|
150
|
+
.delete(schema.contentDatabaseItems)
|
|
151
|
+
.where(inArray(schema.contentDatabaseItems.id, removedItemIds));
|
|
152
|
+
}
|
|
153
|
+
await renumberDatabaseRows(
|
|
154
|
+
tx as unknown as ReturnType<typeof getDb>,
|
|
155
|
+
database,
|
|
156
|
+
now,
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
if (removedItemIds.length > 0) {
|
|
160
|
+
const remaining = await tx
|
|
161
|
+
.select({ id: schema.contentDatabaseItems.id })
|
|
162
|
+
.from(schema.contentDatabaseItems)
|
|
163
|
+
.where(inArray(schema.contentDatabaseItems.id, removedItemIds));
|
|
164
|
+
if (remaining.length > 0) {
|
|
165
|
+
throw new Error("Database memberships were not fully removed.");
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}),
|
|
169
|
+
);
|
|
170
|
+
|
|
171
|
+
await writeAppState("refresh-signal", { ts: Date.now() });
|
|
172
|
+
|
|
173
|
+
return {
|
|
174
|
+
...(await getContentDatabaseResponse(database.id, {
|
|
175
|
+
limit: 100,
|
|
176
|
+
offset: 0,
|
|
177
|
+
})),
|
|
178
|
+
removedItemIds,
|
|
179
|
+
removedDocumentIds,
|
|
180
|
+
removedCount: removedItemIds.length,
|
|
181
|
+
};
|
|
182
|
+
},
|
|
183
|
+
});
|