@agent-native/core 0.154.4 → 0.155.0
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/analytics/.agents/skills/account-health/SKILL.md +76 -0
- package/corpus/templates/analytics/.agents/skills/bigquery/SKILL.md +3 -0
- package/corpus/templates/analytics/.agents/skills/cross-source-analysis/SKILL.md +2 -1
- package/corpus/templates/analytics/.agents/skills/data-querying/SKILL.md +6 -5
- package/corpus/templates/analytics/AGENTS.md +3 -0
- package/corpus/templates/analytics/actions/account-deep-dive.ts +3 -2
- package/corpus/templates/analytics/actions/bigquery.ts +1 -1
- package/corpus/templates/analytics/app/components/dashboard/SqlChart.tsx +4 -1
- package/corpus/templates/analytics/app/components/layout/NewDashboardDialog.tsx +1 -0
- package/corpus/templates/analytics/app/pages/adhoc/explorer/components/PropertyCombobox.tsx +12 -4
- package/corpus/templates/analytics/app/pages/adhoc/explorer/components/PropertyValueCombobox.tsx +5 -2
- package/corpus/templates/analytics/app/pages/adhoc/explorer/property-label.ts +32 -0
- package/corpus/templates/analytics/changelog/2026-08-13-account-health-readouts-verify-customer-scope-and-contract-metrics.md +7 -0
- package/corpus/templates/analytics/server/jobs/analytics-bigquery-backfill.ts +43 -17
- package/corpus/templates/analytics/server/lib/analytics-query-catalog.ts +17 -0
- package/corpus/templates/analytics/server/plugins/agent-chat.ts +4 -0
- package/corpus/templates/calendar/AGENTS.md +2 -0
- package/corpus/templates/calendar/DEVELOPING.md +4 -4
- package/corpus/templates/calendar/actions/get-settings.ts +4 -11
- package/corpus/templates/calendar/actions/update-availability.ts +15 -4
- package/corpus/templates/calendar/actions/update-settings.ts +15 -4
- package/corpus/templates/calendar/app/components/booking/DatePicker.tsx +9 -3
- package/corpus/templates/calendar/app/components/calendar/CreateEventDialog.tsx +21 -19
- package/corpus/templates/calendar/app/components/calendar/EventDetailPopover.tsx +9 -6
- package/corpus/templates/calendar/app/components/calendar/FindTimePanel.tsx +25 -3
- package/corpus/templates/calendar/app/components/calendar/InlineEventPickers.tsx +10 -3
- package/corpus/templates/calendar/app/components/calendar/MonthView.tsx +9 -9
- package/corpus/templates/calendar/app/components/calendar/WeekView.tsx +11 -3
- package/corpus/templates/calendar/app/components/layout/Sidebar.tsx +10 -4
- package/corpus/templates/calendar/app/i18n-data.ts +30 -0
- package/corpus/templates/calendar/app/lib/availability-schedule.ts +53 -0
- package/corpus/templates/calendar/app/lib/event-form-utils.ts +5 -0
- package/corpus/templates/calendar/app/pages/AvailabilitySettings.tsx +90 -28
- package/corpus/templates/calendar/app/pages/BookingLinksPage.tsx +135 -61
- package/corpus/templates/calendar/app/pages/BookingPage.tsx +2 -0
- package/corpus/templates/calendar/app/pages/CalendarView.tsx +30 -15
- package/corpus/templates/calendar/app/pages/Settings.tsx +60 -1
- package/corpus/templates/calendar/changelog/2026-08-13-find-a-time-now-closes-from-a-clear-header-action.md +6 -0
- package/corpus/templates/calendar/server/handlers/bookings.ts +2 -1
- package/corpus/templates/calendar/server/handlers/settings.ts +12 -16
- package/corpus/templates/calendar/shared/api.ts +1 -0
- package/corpus/templates/calendar/shared/availability-schedule.ts +64 -0
- package/corpus/templates/calendar/shared/calendar-week.ts +21 -0
- package/corpus/templates/calendar/shared/settings.ts +44 -0
- package/corpus/templates/clips/AGENTS.md +2 -0
- package/corpus/templates/clips/actions/add-comment.ts +5 -2
- package/corpus/templates/clips/actions/finalize-recording.ts +31 -1
- package/corpus/templates/clips/actions/get-recording-player-data.ts +9 -0
- package/corpus/templates/clips/actions/lib/ensure-seekable-video.ts +21 -1
- package/corpus/templates/clips/actions/reply-to-comment.ts +5 -2
- package/corpus/templates/clips/actions/update-comment.ts +9 -2
- package/corpus/templates/clips/app/components/library/search-bar.tsx +26 -13
- package/corpus/templates/clips/app/components/meetings/share-meeting-dialog.tsx +135 -3
- package/corpus/templates/clips/app/components/player/comments-panel.tsx +5 -99
- package/corpus/templates/clips/app/components/player/playback-comment-overlay.tsx +7 -3
- package/corpus/templates/clips/app/components/player/video-player.tsx +84 -24
- package/corpus/templates/clips/app/components/workspace/notifications-list.tsx +11 -3
- package/corpus/templates/clips/app/i18n/en-US.ts +12 -0
- package/corpus/templates/clips/app/lib/public-meeting.ts +11 -3
- package/corpus/templates/clips/app/lib/search-focus.ts +15 -0
- package/corpus/templates/clips/app/root.tsx +2 -1
- package/corpus/templates/clips/app/routes/_app.meetings._index.tsx +94 -26
- package/corpus/templates/clips/app/routes/_app.settings._index.tsx +24 -7
- package/corpus/templates/clips/app/routes/embed.$shareId.tsx +8 -4
- package/corpus/templates/clips/app/routes/r.$recordingId.tsx +14 -7
- package/corpus/templates/clips/app/routes/share.$shareId.tsx +8 -4
- package/corpus/templates/clips/app/routes/share.meeting.$meetingId.tsx +72 -26
- package/corpus/templates/clips/changelog/2026-08-13-inline-markdown-comments.md +6 -0
- package/corpus/templates/clips/changelog/2026-08-13-private-meeting-agent-links.md +6 -0
- package/corpus/templates/clips/desktop/src/app.tsx +116 -19
- package/corpus/templates/clips/desktop/src/lib/recorder.ts +13 -29
- package/corpus/templates/clips/desktop/src/lib/recording-request.ts +39 -0
- package/corpus/templates/clips/desktop/src/lib/settings-navigation.ts +11 -0
- package/corpus/templates/clips/desktop/src/lib/whisper-model-picker.ts +7 -0
- package/corpus/templates/clips/desktop/src/styles.css +117 -0
- package/corpus/templates/clips/server/db/index.ts +10 -0
- package/corpus/templates/clips/server/db/schema.ts +1 -0
- package/corpus/templates/clips/server/lib/recordings.ts +3 -1
- package/corpus/templates/clips/server/lib/seekable-media-state.ts +90 -0
- package/corpus/templates/clips/server/plugins/db.ts +5 -0
- package/corpus/templates/clips/server/routes/api/public-meeting.get.ts +37 -4
- package/corpus/templates/clips/server/routes/api/public-recording.get.ts +16 -5
- package/corpus/templates/clips/shared/clips-ai-prefs.ts +3 -0
- package/corpus/templates/clips/shared/meeting-agent-access.ts +5 -0
- 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/_database-row-mutation.ts +1490 -0
- package/corpus/templates/content/actions/_database-utils.ts +44 -0
- package/corpus/templates/content/actions/_property-utils.ts +4 -1
- package/corpus/templates/content/actions/add-comment.ts +2 -1
- package/corpus/templates/content/actions/add-database-item.ts +62 -240
- package/corpus/templates/content/actions/configure-document-property.ts +165 -1
- package/corpus/templates/content/actions/delete-document-property.ts +18 -0
- package/corpus/templates/content/actions/set-document-property.ts +78 -1
- package/corpus/templates/content/actions/update-comment.ts +1 -1
- package/corpus/templates/content/actions/update-database-item.ts +62 -0
- package/corpus/templates/content/actions/upsert-database-item-by-key.ts +43 -796
- package/corpus/templates/content/app/components/editor/CommentsSidebar.tsx +24 -15
- 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/changelog/2026-08-10-database-row-actions-now-validate-exact-schemas-and-safe-ret.md +6 -0
- 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 +39 -3
- package/corpus/templates/content/server/plugins/db.ts +30 -0
- package/corpus/templates/content/shared/api.ts +75 -2
- package/corpus/templates/design/actions/generate-design.ts +10 -7
- package/corpus/templates/design/actions/present-design-variants.ts +35 -6
- package/corpus/templates/design/actions/run-design-audit.ts +33 -0
- package/corpus/templates/design/changelog/2026-08-13-the-design-audit-now-catches-a-screen-that-renders-behind-an.md +6 -0
- package/corpus/templates/design/shared/design-review.ts +1 -0
- package/corpus/templates/design/shared/html-integrity.ts +66 -0
- package/corpus/templates/dispatch/.env.example +5 -0
- package/corpus/templates/dispatch/AGENTS.md +24 -19
- package/corpus/templates/dispatch/app/i18n-data.ts +20 -0
- package/corpus/templates/dispatch/server/lib/identity-sso.ts +21 -90
- package/corpus/templates/dispatch/server/plugins/identity-sso.ts +3 -12
- package/corpus/templates/dispatch/shared/feature-flags.ts +4 -0
- package/corpus/templates/factory/AGENTS.md +5 -1
- package/corpus/templates/factory/README.md +23 -0
- package/corpus/templates/factory/actions/view-screen.ts +17 -0
- package/corpus/templates/factory/app/components/factory/FactoryAgentsView.tsx +239 -0
- package/corpus/templates/factory/app/components/factory/FactoryCanvas.tsx +2 -0
- package/corpus/templates/factory/app/components/factory/FactoryInspector.tsx +130 -7
- package/corpus/templates/factory/app/i18n/en-US.ts +20 -0
- package/corpus/templates/factory/app/routes/factory.tsx +41 -0
- package/corpus/templates/factory/package.json +1 -0
- package/corpus/templates/factory/server/factory-graph/contracts.ts +2 -0
- package/corpus/templates/factory/server/plugins/agent-chat.ts +9 -0
- package/corpus/templates/factory/server/plugins/dispatch-db.ts +1 -0
- package/corpus/templates/factory/server/plugins/setup-dispatch.ts +3 -0
- package/corpus/templates/mail/app/lib/native-mcp-exclusions.ts +3 -0
- package/corpus/templates/mail/vite.config.ts +7 -0
- package/corpus/templates/plan/actions/reply-to-plan-comment.ts +7 -2
- package/corpus/templates/plan/actions/resolve-plan-comment.ts +2 -2
- package/corpus/templates/plan/actions/update-visual-plan.ts +1 -1
- package/corpus/templates/plan/app/pages/PlansPage.tsx +29 -20
- package/corpus/templates/slides/actions/add-slide-comment.ts +1 -1
- package/corpus/templates/slides/actions/update-slide-comment.ts +1 -1
- package/corpus/templates/slides/app/components/comments/SlideCommentsPanel.tsx +9 -6
- package/dist/action.d.ts +22 -0
- package/dist/action.js +27 -0
- package/dist/agent/production-agent.js +5 -1
- package/dist/agent/thread-data-builder.js +17 -1
- package/dist/cli/skills-content/help.js +6 -4
- package/dist/cli/skills.js +17 -2
- package/dist/cli/workspacify.d.ts +5 -0
- package/dist/cli/workspacify.js +65 -8
- package/dist/client/AgentPanel.d.ts +5 -0
- package/dist/client/AgentPanel.js +20 -4
- package/dist/client/AssistantChat.d.ts +21 -0
- package/dist/client/AssistantChat.js +66 -68
- package/dist/client/active-run-state.d.ts +14 -1
- package/dist/client/active-run-state.js +0 -0
- package/dist/client/agent-chat-adapter.js +107 -5
- package/dist/client/analytics.js +9 -2
- package/dist/client/chat/tool-call-display.js +4 -4
- package/dist/client/chat-first/apps-rail.js +9 -4
- package/dist/client/chat-first/primary-nav.d.ts +2 -1
- package/dist/client/chat-first/primary-nav.js +3 -3
- package/dist/client/chat-first/types.d.ts +5 -1
- package/dist/client/markdown/InlineMarkdown.d.ts +23 -0
- package/dist/client/markdown/InlineMarkdown.js +65 -0
- package/dist/client/markdown/index.d.ts +1 -0
- package/dist/client/markdown/index.js +1 -0
- package/dist/client/review/ReviewThreadPanel.js +2 -1
- package/dist/client/run-stream-ownership.d.ts +39 -0
- package/dist/client/run-stream-ownership.js +65 -0
- package/dist/collab/routes.d.ts +1 -1
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/feature-flags/index.d.ts +1 -1
- package/dist/feature-flags/index.js +1 -1
- package/dist/feature-flags/store.d.ts +10 -0
- package/dist/feature-flags/store.js +112 -1
- package/dist/guards/no-env-credentials.js +4 -0
- package/dist/index.browser.d.ts +1 -1
- package/dist/index.browser.js +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/integrations/adapters/slack.js +5 -1
- package/dist/localization/default-messages.d.ts +1 -0
- package/dist/localization/default-messages.js +1 -0
- package/dist/notifications/routes.d.ts +1 -1
- package/dist/observability/routes.d.ts +1 -1
- package/dist/progress/routes.d.ts +1 -1
- package/dist/provider-api/actions/custom-provider-registration.d.ts +11 -11
- package/dist/provider-api/actions/provider-api.d.ts +15 -15
- package/dist/resources/agents.js +40 -5
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/resources/metadata.d.ts +10 -0
- package/dist/resources/metadata.js +7 -3
- package/dist/review/actions/create-review-comment.js +1 -1
- package/dist/review/actions/reply-review-comment.js +1 -1
- package/dist/review/actions/resolve-review-thread.js +1 -1
- package/dist/server/action-routes.js +42 -8
- package/dist/server/agent-discovery.js +14 -3
- package/dist/server/core-routes-plugin.js +1 -1
- package/dist/server/identity-sso-migrations.d.ts +7 -0
- package/dist/server/identity-sso-migrations.js +36 -0
- package/dist/server/identity-sso-store.d.ts +3 -2
- package/dist/server/identity-sso-store.js +8 -3
- package/dist/server/realtime-token.d.ts +1 -1
- package/dist/server/release-migrations.js +4 -0
- package/dist/tracking/providers.js +13 -3
- package/dist/workspace-files/tool.js +1 -1
- package/package.json +3 -2
|
@@ -45,6 +45,10 @@ import {
|
|
|
45
45
|
normalizeContentSpaceEmail,
|
|
46
46
|
resolveContentSpaceAccess,
|
|
47
47
|
} from "./_content-space-access.js";
|
|
48
|
+
import {
|
|
49
|
+
databaseRowRevision,
|
|
50
|
+
getDatabaseMutationContract,
|
|
51
|
+
} from "./_database-row-mutation.js";
|
|
48
52
|
import { getAllContentDatabaseSourceSnapshots } from "./_database-source-utils.js";
|
|
49
53
|
import {
|
|
50
54
|
applyFederatedOverlayValues,
|
|
@@ -1050,6 +1054,21 @@ export async function getContentDatabasePageResponse(
|
|
|
1050
1054
|
queued: bodyHydrationQueued,
|
|
1051
1055
|
}),
|
|
1052
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
|
+
}),
|
|
1053
1072
|
});
|
|
1054
1073
|
}
|
|
1055
1074
|
|
|
@@ -1195,6 +1214,26 @@ export async function getContentDatabaseResponse(
|
|
|
1195
1214
|
sources: page.sources,
|
|
1196
1215
|
pagination: page.pagination,
|
|
1197
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,
|
|
1198
1237
|
};
|
|
1199
1238
|
}
|
|
1200
1239
|
|
|
@@ -1470,6 +1509,11 @@ export async function deleteDatabaseDataForDocument(
|
|
|
1470
1509
|
.where(
|
|
1471
1510
|
eq(schema.contentDatabaseMigrationReceipts.databaseId, database.id),
|
|
1472
1511
|
);
|
|
1512
|
+
await db
|
|
1513
|
+
.delete(schema.contentDatabaseRowMutationReceipts)
|
|
1514
|
+
.where(
|
|
1515
|
+
eq(schema.contentDatabaseRowMutationReceipts.databaseId, database.id),
|
|
1516
|
+
);
|
|
1473
1517
|
await db
|
|
1474
1518
|
.delete(schema.documentPropertyDefinitions)
|
|
1475
1519
|
.where(eq(schema.documentPropertyDefinitions.databaseId, database.id));
|
|
@@ -65,7 +65,8 @@ type ContentDatabaseSummaryRow = Pick<
|
|
|
65
65
|
| "viewConfigJson"
|
|
66
66
|
| "createdAt"
|
|
67
67
|
| "updatedAt"
|
|
68
|
-
|
|
68
|
+
> &
|
|
69
|
+
Partial<Pick<ContentDatabaseRow, "spaceId" | "naturalKeyPropertyId">>;
|
|
69
70
|
type ContentDatabaseItemRow = InferSelectModel<
|
|
70
71
|
typeof schema.contentDatabaseItems
|
|
71
72
|
>;
|
|
@@ -199,8 +200,10 @@ export function serializeDatabase(
|
|
|
199
200
|
return {
|
|
200
201
|
id: database.id,
|
|
201
202
|
documentId: database.documentId,
|
|
203
|
+
spaceId: database.spaceId,
|
|
202
204
|
title: database.title,
|
|
203
205
|
systemRole: database.systemRole,
|
|
206
|
+
naturalKeyPropertyId: database.naturalKeyPropertyId,
|
|
204
207
|
description,
|
|
205
208
|
viewConfig: parseDatabaseViewConfig(database.viewConfigJson),
|
|
206
209
|
createdAt: database.createdAt,
|
|
@@ -44,7 +44,8 @@ function displayNameFromEmail(email: string): string {
|
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
export default defineAction({
|
|
47
|
-
description:
|
|
47
|
+
description:
|
|
48
|
+
"Add a comment to a document. Comment text supports inline Markdown for emphasis, inline code, links, and line breaks; headings are flattened. For new threads, omit threadId.",
|
|
48
49
|
schema: z.object({
|
|
49
50
|
documentId: z.string().optional().describe("Document ID (required)"),
|
|
50
51
|
content: z.string().optional().describe("Comment text (required)"),
|
|
@@ -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
|
});
|
|
@@ -52,6 +52,12 @@ export default defineAction({
|
|
|
52
52
|
"Stable guidance describing what this property means and which value belongs here",
|
|
53
53
|
),
|
|
54
54
|
type: z.enum(CREATABLE_DOCUMENT_PROPERTY_TYPES).describe("Property type"),
|
|
55
|
+
naturalKey: z
|
|
56
|
+
.boolean()
|
|
57
|
+
.optional()
|
|
58
|
+
.describe(
|
|
59
|
+
"Declare or clear this ordinary text property as the database's single natural key",
|
|
60
|
+
),
|
|
55
61
|
visibility: z
|
|
56
62
|
.enum(DOCUMENT_PROPERTY_VISIBILITIES)
|
|
57
63
|
.optional()
|
|
@@ -104,6 +110,7 @@ export default defineAction({
|
|
|
104
110
|
const now = new Date().toISOString();
|
|
105
111
|
const name = args.name.trim();
|
|
106
112
|
const type = args.type as DocumentPropertyType;
|
|
113
|
+
const propertyId = args.id ?? nanoid();
|
|
107
114
|
const optionsJson = optionsForNewProperty(type, args.options as any);
|
|
108
115
|
const database = await resolvePropertyDatabaseForDocument(
|
|
109
116
|
document,
|
|
@@ -115,6 +122,14 @@ export default defineAction({
|
|
|
115
122
|
"Properties belong to databases. Create or open a database before adding properties.",
|
|
116
123
|
);
|
|
117
124
|
}
|
|
125
|
+
if (args.naturalKey === true && type !== "text") {
|
|
126
|
+
throw new Error(
|
|
127
|
+
"A database natural key must be an ordinary text property.",
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
if (args.naturalKey !== undefined && database.systemRole) {
|
|
131
|
+
throw new Error("System databases cannot configure a natural key.");
|
|
132
|
+
}
|
|
118
133
|
|
|
119
134
|
if (args.id) {
|
|
120
135
|
const [existing] = await db
|
|
@@ -136,6 +151,11 @@ export default defineAction({
|
|
|
136
151
|
tx as unknown as ReturnType<typeof getDb>,
|
|
137
152
|
database.id,
|
|
138
153
|
);
|
|
154
|
+
const [lockedDatabase] = await tx
|
|
155
|
+
.select()
|
|
156
|
+
.from(schema.contentDatabases)
|
|
157
|
+
.where(eq(schema.contentDatabases.id, database.id));
|
|
158
|
+
if (!lockedDatabase) throw new Error("Database not found.");
|
|
139
159
|
let [lockedDefinition] = await tx
|
|
140
160
|
.select()
|
|
141
161
|
.from(schema.documentPropertyDefinitions)
|
|
@@ -151,6 +171,14 @@ export default defineAction({
|
|
|
151
171
|
);
|
|
152
172
|
if (!lockedDefinition)
|
|
153
173
|
throw new Error(`Property "${args.id}" not found`);
|
|
174
|
+
if (
|
|
175
|
+
lockedDatabase.naturalKeyPropertyId === args.id &&
|
|
176
|
+
type !== "text"
|
|
177
|
+
) {
|
|
178
|
+
throw new Error(
|
|
179
|
+
"Clear the database natural key before changing this property's type.",
|
|
180
|
+
);
|
|
181
|
+
}
|
|
154
182
|
if (lockedDefinition.systemRole) {
|
|
155
183
|
throw new Error("System properties cannot be changed.");
|
|
156
184
|
}
|
|
@@ -265,6 +293,13 @@ export default defineAction({
|
|
|
265
293
|
updatedAt: now,
|
|
266
294
|
})
|
|
267
295
|
.where(eq(schema.documentPropertyDefinitions.id, args.id!));
|
|
296
|
+
await configureNaturalKey(tx, {
|
|
297
|
+
database: lockedDatabase,
|
|
298
|
+
propertyId,
|
|
299
|
+
naturalKey: args.naturalKey,
|
|
300
|
+
ownerEmail: document.ownerEmail,
|
|
301
|
+
now,
|
|
302
|
+
});
|
|
268
303
|
});
|
|
269
304
|
} else {
|
|
270
305
|
await withPositionLock(
|
|
@@ -275,6 +310,11 @@ export default defineAction({
|
|
|
275
310
|
tx as unknown as ReturnType<typeof getDb>,
|
|
276
311
|
database.id,
|
|
277
312
|
);
|
|
313
|
+
const [lockedDatabase] = await tx
|
|
314
|
+
.select()
|
|
315
|
+
.from(schema.contentDatabases)
|
|
316
|
+
.where(eq(schema.contentDatabases.id, database.id));
|
|
317
|
+
if (!lockedDatabase) throw new Error("Database not found.");
|
|
278
318
|
const [maxPos] = await tx
|
|
279
319
|
.select({
|
|
280
320
|
max: sql<number>`COALESCE(MAX(position), -1)`,
|
|
@@ -294,7 +334,7 @@ export default defineAction({
|
|
|
294
334
|
);
|
|
295
335
|
|
|
296
336
|
await tx.insert(schema.documentPropertyDefinitions).values({
|
|
297
|
-
id:
|
|
337
|
+
id: propertyId,
|
|
298
338
|
ownerEmail: document.ownerEmail,
|
|
299
339
|
orgId: document.orgId ?? null,
|
|
300
340
|
databaseId: database.id,
|
|
@@ -307,6 +347,13 @@ export default defineAction({
|
|
|
307
347
|
createdAt: now,
|
|
308
348
|
updatedAt: now,
|
|
309
349
|
});
|
|
350
|
+
await configureNaturalKey(tx, {
|
|
351
|
+
database: lockedDatabase,
|
|
352
|
+
propertyId,
|
|
353
|
+
naturalKey: args.naturalKey,
|
|
354
|
+
ownerEmail: document.ownerEmail,
|
|
355
|
+
now,
|
|
356
|
+
});
|
|
310
357
|
});
|
|
311
358
|
},
|
|
312
359
|
);
|
|
@@ -321,3 +368,120 @@ export default defineAction({
|
|
|
321
368
|
};
|
|
322
369
|
},
|
|
323
370
|
});
|
|
371
|
+
|
|
372
|
+
async function configureNaturalKey(
|
|
373
|
+
tx: Parameters<Parameters<ReturnType<typeof getDb>["transaction"]>[0]>[0],
|
|
374
|
+
args: {
|
|
375
|
+
database: typeof schema.contentDatabases.$inferSelect;
|
|
376
|
+
propertyId: string;
|
|
377
|
+
naturalKey: boolean | undefined;
|
|
378
|
+
ownerEmail: string;
|
|
379
|
+
now: string;
|
|
380
|
+
},
|
|
381
|
+
) {
|
|
382
|
+
if (args.naturalKey === undefined) return;
|
|
383
|
+
if (!args.naturalKey) {
|
|
384
|
+
if (args.database.naturalKeyPropertyId === args.propertyId) {
|
|
385
|
+
await tx
|
|
386
|
+
.delete(schema.contentDatabaseItemKeyClaims)
|
|
387
|
+
.where(
|
|
388
|
+
and(
|
|
389
|
+
eq(
|
|
390
|
+
schema.contentDatabaseItemKeyClaims.databaseId,
|
|
391
|
+
args.database.id,
|
|
392
|
+
),
|
|
393
|
+
eq(schema.contentDatabaseItemKeyClaims.propertyId, args.propertyId),
|
|
394
|
+
),
|
|
395
|
+
);
|
|
396
|
+
await tx
|
|
397
|
+
.update(schema.contentDatabases)
|
|
398
|
+
.set({ naturalKeyPropertyId: null, updatedAt: args.now })
|
|
399
|
+
.where(eq(schema.contentDatabases.id, args.database.id));
|
|
400
|
+
}
|
|
401
|
+
return;
|
|
402
|
+
}
|
|
403
|
+
if (
|
|
404
|
+
args.database.naturalKeyPropertyId &&
|
|
405
|
+
args.database.naturalKeyPropertyId !== args.propertyId
|
|
406
|
+
) {
|
|
407
|
+
throw new Error(
|
|
408
|
+
"Clear the existing database natural key before configuring another one.",
|
|
409
|
+
);
|
|
410
|
+
}
|
|
411
|
+
const [sourceField] = await tx
|
|
412
|
+
.select({ id: schema.contentDatabaseSourceFields.id })
|
|
413
|
+
.from(schema.contentDatabaseSourceFields)
|
|
414
|
+
.where(eq(schema.contentDatabaseSourceFields.propertyId, args.propertyId))
|
|
415
|
+
.limit(1);
|
|
416
|
+
if (sourceField) {
|
|
417
|
+
throw new Error("A source-managed property cannot be a natural key.");
|
|
418
|
+
}
|
|
419
|
+
const values = await tx
|
|
420
|
+
.select({
|
|
421
|
+
valueJson: schema.documentPropertyValues.valueJson,
|
|
422
|
+
itemId: schema.contentDatabaseItems.id,
|
|
423
|
+
documentId: schema.contentDatabaseItems.documentId,
|
|
424
|
+
})
|
|
425
|
+
.from(schema.contentDatabaseItems)
|
|
426
|
+
.innerJoin(
|
|
427
|
+
schema.documentPropertyValues,
|
|
428
|
+
and(
|
|
429
|
+
eq(
|
|
430
|
+
schema.documentPropertyValues.documentId,
|
|
431
|
+
schema.contentDatabaseItems.documentId,
|
|
432
|
+
),
|
|
433
|
+
eq(schema.documentPropertyValues.propertyId, args.propertyId),
|
|
434
|
+
),
|
|
435
|
+
)
|
|
436
|
+
.where(eq(schema.contentDatabaseItems.databaseId, args.database.id));
|
|
437
|
+
const claims = new Map<string, (typeof values)[number]>();
|
|
438
|
+
for (const value of values) {
|
|
439
|
+
let parsed: unknown;
|
|
440
|
+
try {
|
|
441
|
+
parsed = JSON.parse(value.valueJson);
|
|
442
|
+
} catch {
|
|
443
|
+
throw new Error("Natural key values must be readable strings.");
|
|
444
|
+
}
|
|
445
|
+
if (parsed === null || parsed === "") continue;
|
|
446
|
+
if (typeof parsed !== "string") {
|
|
447
|
+
throw new Error("Natural key values must be non-empty strings.");
|
|
448
|
+
}
|
|
449
|
+
if (!parsed.trim()) {
|
|
450
|
+
throw new Error("Natural key values must be non-empty strings.");
|
|
451
|
+
}
|
|
452
|
+
if (claims.has(value.valueJson)) {
|
|
453
|
+
throw new Error(
|
|
454
|
+
`Natural key value ${value.valueJson} belongs to more than one row.`,
|
|
455
|
+
);
|
|
456
|
+
}
|
|
457
|
+
claims.set(value.valueJson, value);
|
|
458
|
+
}
|
|
459
|
+
await tx
|
|
460
|
+
.delete(schema.contentDatabaseItemKeyClaims)
|
|
461
|
+
.where(
|
|
462
|
+
and(
|
|
463
|
+
eq(schema.contentDatabaseItemKeyClaims.databaseId, args.database.id),
|
|
464
|
+
eq(schema.contentDatabaseItemKeyClaims.propertyId, args.propertyId),
|
|
465
|
+
),
|
|
466
|
+
);
|
|
467
|
+
if (claims.size > 0) {
|
|
468
|
+
await tx.insert(schema.contentDatabaseItemKeyClaims).values(
|
|
469
|
+
[...claims.entries()].map(([keyValueJson, value]) => ({
|
|
470
|
+
id: nanoid(),
|
|
471
|
+
ownerEmail: args.ownerEmail,
|
|
472
|
+
orgId: args.database.orgId,
|
|
473
|
+
databaseId: args.database.id,
|
|
474
|
+
propertyId: args.propertyId,
|
|
475
|
+
keyValueJson,
|
|
476
|
+
itemId: value.itemId,
|
|
477
|
+
documentId: value.documentId,
|
|
478
|
+
createdAt: args.now,
|
|
479
|
+
updatedAt: args.now,
|
|
480
|
+
})),
|
|
481
|
+
);
|
|
482
|
+
}
|
|
483
|
+
await tx
|
|
484
|
+
.update(schema.contentDatabases)
|
|
485
|
+
.set({ naturalKeyPropertyId: args.propertyId, updatedAt: args.now })
|
|
486
|
+
.where(eq(schema.contentDatabases.id, args.database.id));
|
|
487
|
+
}
|
|
@@ -69,6 +69,14 @@ export default defineAction({
|
|
|
69
69
|
tx as unknown as ReturnType<typeof getDb>,
|
|
70
70
|
database.id,
|
|
71
71
|
);
|
|
72
|
+
const [lockedDatabase] = await tx
|
|
73
|
+
.select({
|
|
74
|
+
naturalKeyPropertyId: schema.contentDatabases.naturalKeyPropertyId,
|
|
75
|
+
})
|
|
76
|
+
.from(schema.contentDatabases)
|
|
77
|
+
.where(eq(schema.contentDatabases.id, database.id));
|
|
78
|
+
if (!lockedDatabase)
|
|
79
|
+
throw new Error(`Database "${database.id}" not found`);
|
|
72
80
|
const [lockedDefinition] = await tx
|
|
73
81
|
.select()
|
|
74
82
|
.from(schema.documentPropertyDefinitions)
|
|
@@ -131,6 +139,16 @@ export default defineAction({
|
|
|
131
139
|
.delete(schema.documentPropertyDefinitions)
|
|
132
140
|
.where(eq(schema.documentPropertyDefinitions.id, propertyId));
|
|
133
141
|
|
|
142
|
+
if (lockedDatabase.naturalKeyPropertyId === propertyId) {
|
|
143
|
+
await tx
|
|
144
|
+
.update(schema.contentDatabases)
|
|
145
|
+
.set({
|
|
146
|
+
naturalKeyPropertyId: null,
|
|
147
|
+
updatedAt: new Date().toISOString(),
|
|
148
|
+
})
|
|
149
|
+
.where(eq(schema.contentDatabases.id, database.id));
|
|
150
|
+
}
|
|
151
|
+
|
|
134
152
|
if (isBlocks) {
|
|
135
153
|
await deleteBlocksFieldIdentity({
|
|
136
154
|
db: tx as unknown as ReturnType<typeof getDb>,
|