@agent-native/core 0.136.5 → 0.137.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/templates/analytics/app/components/AgentCompletionSound.tsx +105 -0
- package/corpus/templates/analytics/app/components/layout/Layout.tsx +44 -35
- package/corpus/templates/analytics/app/i18n-data.ts +38 -0
- package/corpus/templates/analytics/app/pages/Settings.tsx +53 -1
- package/corpus/templates/analytics/app/pages/settings/settings-search.ts +6 -0
- package/corpus/templates/analytics/changelog/2026-08-04-choose-whether-analytics-plays-a-bell-when-the-agent-finishe.md +6 -0
- package/corpus/templates/analytics/shared/analytics-user-prefs.ts +2 -0
- package/corpus/templates/clips/app/components/import-menu.tsx +109 -0
- package/corpus/templates/clips/app/components/library/library-grid.tsx +8 -18
- package/corpus/templates/clips/app/components/library/library-layout.tsx +14 -50
- package/corpus/templates/clips/app/components/library/sort-menu.tsx +20 -6
- package/corpus/templates/clips/app/components/recorder/pre-record-panel.tsx +10 -34
- package/corpus/templates/clips/app/routes/_app.library._index.tsx +0 -2
- package/corpus/templates/clips/changelog/2026-08-04-cleaner-library-controls-with-an-icon-only-sort-button.md +6 -0
- package/corpus/templates/clips/changelog/2026-08-04-upload-and-loom-imports-now-live-under-one-import-menu.md +6 -0
- package/corpus/templates/content/actions/_database-utils.ts +6 -0
- package/corpus/templates/content/actions/bind-content-database-source-field.ts +273 -97
- package/corpus/templates/content/actions/change-content-database-source-role.ts +65 -29
- package/corpus/templates/content/actions/configure-document-property.ts +113 -32
- package/corpus/templates/content/actions/delete-content-database.ts +5 -7
- package/corpus/templates/content/actions/delete-document-property.ts +109 -62
- package/corpus/templates/content/actions/delete-document.ts +178 -36
- package/corpus/templates/content/actions/duplicate-database-item.ts +27 -0
- package/corpus/templates/content/actions/duplicate-database-items.ts +31 -1
- package/corpus/templates/content/actions/permanently-delete-document.ts +4 -6
- package/corpus/templates/content/actions/remove-database-items.ts +13 -0
- package/corpus/templates/content/actions/set-document-property.ts +69 -1
- package/corpus/templates/content/actions/upsert-database-item-by-key.ts +814 -0
- package/corpus/templates/content/parity/matrix.md +1 -1
- package/corpus/templates/content/parity/matrix.ts +1 -0
- package/corpus/templates/content/server/db/schema.ts +27 -0
- package/corpus/templates/content/server/plugins/db.ts +24 -0
- package/corpus/templates/design/app/components/design/BreakpointBar.tsx +122 -114
- package/corpus/templates/design/app/components/design/CanvasContextMenu.tsx +2 -2
- package/corpus/templates/design/app/components/design/EditPanel.tsx +68 -34
- package/corpus/templates/design/app/components/design/KeyboardShortcutsPanel.tsx +78 -4
- package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +8 -0
- package/corpus/templates/design/app/components/design/QuestionFlow.tsx +5 -2
- package/corpus/templates/design/app/components/design/edit-panel/panel-primitives.tsx +10 -9
- package/corpus/templates/design/app/components/design/inspector/AutoLayoutMatrix.tsx +31 -69
- package/corpus/templates/design/app/components/design/inspector/design-icons.tsx +80 -106
- package/corpus/templates/design/app/components/design/multi-screen/types.ts +4 -0
- package/corpus/templates/design/app/hooks/use-editor-preferences.ts +75 -0
- package/corpus/templates/design/app/i18n-data.ts +8 -0
- package/corpus/templates/design/app/i18n-keyboard-shortcuts.ts +61 -0
- package/corpus/templates/design/app/pages/design-editor/editor-preferences.ts +98 -0
- package/corpus/templates/design/app/pages/design-editor/nudge-intent.ts +491 -0
- package/corpus/templates/design/app/pages/design-editor/paste-placement.ts +133 -0
- package/corpus/templates/design/app/pages/design-editor/png-export-render.ts +8 -0
- package/corpus/templates/design/app/pages/design-editor/selection-state.ts +5 -12
- package/corpus/templates/design/changelog/2026-08-03-arrow-keys-now-reorder-a-layer-inside-an-auto-layout-frame.md +6 -0
- package/corpus/templates/design/changelog/2026-08-03-auto-layout-flow-icons-show-the-direction-at-a-glance-instea.md +6 -0
- package/corpus/templates/design/changelog/2026-08-03-auto-layout-icons-share-one-outlined-square-style-so-flow-ga.md +6 -0
- package/corpus/templates/design/changelog/2026-08-03-export-has-a-named-preview-section-you-can-expand-replacing-.md +6 -0
- package/corpus/templates/design/changelog/2026-08-03-export-preview-shows-the-selected-element-instead-of-the-who.md +6 -0
- package/corpus/templates/design/changelog/2026-08-03-inspector-sections-show-an-expand-indicator-so-it-is-clear-w.md +6 -0
- package/corpus/templates/design/changelog/2026-08-03-paste-lands-inside-a-selected-frame-instead-of-beside-it.md +6 -0
- package/corpus/templates/design/changelog/2026-08-03-the-add-breakpoint-button-sits-outside-the-breakpoint-select.md +6 -0
- package/corpus/templates/design/changelog/2026-08-03-the-agent-s-follow-up-questions-keep-space-below-the-continu.md +6 -0
- package/corpus/templates/design/changelog/2026-08-03-the-agent-s-follow-up-questions-share-the-chat-panel-s-backg.md +6 -0
- package/corpus/templates/design/changelog/2026-08-03-the-export-panel-no-longer-stacks-a-duplicate-export-block-o.md +6 -0
- package/corpus/templates/design/changelog/2026-08-03-the-text-tool-uses-a-t-icon-matching-the-letter-it-inserts.md +6 -0
- package/corpus/templates/factory/.env.example +17 -0
- package/corpus/templates/factory/AGENTS.md +27 -17
- package/corpus/templates/factory/README.md +19 -9
- package/corpus/templates/factory/actions/get-slack-feedback-context.ts +90 -0
- package/corpus/templates/factory/actions/get-triage-config.ts +14 -3
- package/corpus/templates/factory/actions/govern-agent-native-pull-request.ts +386 -0
- package/corpus/templates/factory/actions/list-factory-automations.ts +53 -0
- package/corpus/templates/factory/actions/list-triage-items.ts +7 -2
- package/corpus/templates/factory/actions/poll-github-sources.ts +203 -0
- package/corpus/templates/factory/actions/poll-sentry-errors.ts +161 -0
- package/corpus/templates/factory/actions/poll-slack-channel.ts +4 -2
- package/corpus/templates/factory/actions/run-factory-automation.ts +42 -0
- package/corpus/templates/factory/actions/save-factory-automation.ts +69 -0
- package/corpus/templates/factory/actions/save-triage-config.ts +28 -2
- package/corpus/templates/factory/actions/start-builder-for-item.ts +411 -0
- package/corpus/templates/factory/app/components/factory/FactoryCanvas.tsx +14 -3
- package/corpus/templates/factory/app/components/ui/checkbox.tsx +1 -0
- package/corpus/templates/factory/app/hooks/use-navigation-state.ts +7 -0
- package/corpus/templates/factory/app/i18n/en-US.ts +35 -1
- package/corpus/templates/factory/app/routes/factory.tsx +462 -9
- package/corpus/templates/factory/changelog/2026-08-04-added-organization-owned-factory-automations-for-slack-bug-t.md +6 -0
- package/corpus/templates/factory/changelog/2026-08-04-automation-editor-controls-and-model-picker.md +6 -0
- package/corpus/templates/factory/netlify.toml +2 -2
- package/corpus/templates/factory/server/connectors/credentials.ts +6 -5
- package/corpus/templates/factory/server/connectors/slack.ts +133 -5
- package/corpus/templates/factory/server/db/schema.ts +6 -0
- package/corpus/templates/factory/server/lib/require-factory-automation.ts +65 -0
- package/corpus/templates/factory/server/plugins/agent-chat.ts +29 -11
- package/corpus/templates/factory/server/plugins/auth.ts +1 -0
- package/corpus/templates/factory/server/plugins/factory-migrations.ts +12 -0
- package/corpus/templates/factory/server/plugins/factory-scheduler-job.ts +218 -67
- package/corpus/templates/factory/server/triage/builder-executor.ts +4 -0
- package/corpus/templates/factory/server/triage/contracts.ts +14 -1
- package/corpus/templates/factory/server/triage/github-client.ts +404 -0
- package/corpus/templates/factory/server/triage/metadata.ts +44 -0
- package/corpus/templates/factory/server/triage/pr-babysit.ts +5 -5
- package/corpus/templates/factory/server/triage/pr-policy.ts +144 -0
- package/corpus/templates/factory/server/triage/sentry-client.ts +180 -0
- package/corpus/templates/factory/server/triage/slack-client.ts +43 -0
- package/corpus/templates/factory/server/triage/slack-poller.ts +9 -2
- package/corpus/templates/plan/.agents/skills/visual-plan/SKILL.md +27 -0
- package/corpus/templates/plan/.agents/skills/visual-plan/references/canvas.md +14 -4
- package/corpus/templates/plan/.agents/skills/visual-plan/references/wireframe.md +7 -0
- package/corpus/templates/plan/.agents/skills/visual-recap/references/wireframe.md +7 -0
- package/corpus/templates/plan/actions/create-plan-design.ts +8 -6
- package/corpus/templates/plan/actions/create-prototype-plan.ts +8 -6
- package/corpus/templates/plan/actions/create-ui-plan.ts +7 -7
- package/corpus/templates/plan/actions/get-plan-blocks.ts +7 -1
- package/corpus/templates/plan/actions/validate-plan-input.ts +39 -0
- package/corpus/templates/plan/app/components/plan/CanvasArea.tsx +167 -11
- package/corpus/templates/plan/app/i18n/en-US.ts +1 -0
- package/corpus/templates/plan/changelog/2026-08-04-canvas-zoom-controls-now-explain-the-command-ctrl-plus-scrol.md +6 -0
- package/corpus/templates/plan/changelog/2026-08-04-pinch-to-zoom-is-now-supported-on-touchscreens-in-visual-pla.md +6 -0
- package/corpus/templates/plan/shared/plan-content.ts +17 -2
- package/corpus/templates/slides/app/components/editor/NewDeckReferenceStep.tsx +321 -0
- package/corpus/templates/slides/app/components/editor/PromptDialog.tsx +32 -11
- package/corpus/templates/slides/app/lib/recent-references.ts +82 -0
- package/corpus/templates/slides/app/pages/DeckEditor.tsx +3 -0
- package/corpus/templates/slides/app/pages/Index.tsx +236 -181
- package/dist/cli/skills-content/canvas.d.ts +3 -3
- package/dist/cli/skills-content/canvas.js +16 -5
- package/dist/cli/skills-content/visual-plan-skill.d.ts +1 -1
- package/dist/cli/skills-content/visual-plan-skill.js +27 -0
- package/dist/cli/skills-content/wireframe.d.ts +2 -2
- package/dist/cli/skills-content/wireframe.js +7 -0
- package/dist/client/AgentPanel.js +23 -8
- package/dist/client/DefaultSpinner.d.ts +0 -12
- package/dist/client/DefaultSpinner.js +22 -2
- package/dist/client/api-path.d.ts +6 -0
- package/dist/client/api-path.js +55 -0
- package/dist/client/onboarding/FirstRunOnboarding.d.ts +3 -0
- package/dist/client/onboarding/FirstRunOnboarding.js +93 -0
- package/dist/client/onboarding/first-run-enabled.d.ts +2 -0
- package/dist/client/onboarding/first-run-enabled.js +9 -0
- package/dist/client/onboarding/index.d.ts +2 -1
- package/dist/client/onboarding/index.js +1 -0
- package/dist/client/onboarding/use-onboarding.d.ts +6 -1
- package/dist/client/onboarding/use-onboarding.js +35 -1
- package/dist/client/route-state.js +10 -0
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/routes.d.ts +1 -1
- package/dist/connections/catalog.d.ts +4 -4
- package/dist/connections/catalog.js +3 -3
- package/dist/db/index.d.ts +1 -1
- package/dist/db/index.js +1 -1
- package/dist/deploy/build.js +2 -1
- package/dist/notifications/routes.d.ts +3 -3
- package/dist/observability/routes.d.ts +1 -1
- package/dist/onboarding/app-profile.d.ts +4 -0
- package/dist/onboarding/app-profile.js +378 -0
- package/dist/onboarding/index.d.ts +2 -1
- package/dist/onboarding/index.js +1 -0
- package/dist/onboarding/plugin.d.ts +2 -0
- package/dist/onboarding/plugin.js +21 -1
- package/dist/onboarding/types.d.ts +19 -0
- package/dist/progress/routes.d.ts +1 -1
- package/dist/provider-api/actions/custom-provider-registration.d.ts +9 -9
- package/dist/provider-api/actions/provider-api.d.ts +12 -12
- package/dist/secrets/routes.d.ts +3 -3
- package/dist/server/auth.js +21 -1
- package/dist/server/better-auth-instance.d.ts +3 -2
- package/dist/server/better-auth-instance.js +9 -7
- package/dist/server/email-markdown.js +3 -8
- package/dist/shared/first-run-onboarding.d.ts +3 -0
- package/dist/shared/first-run-onboarding.js +3 -0
- package/dist/templates/workspace-core/.agents/skills/address-feedback/SKILL.md +25 -9
- package/dist/triggers/index.d.ts +3 -0
- package/dist/triggers/index.js +5 -0
- package/docs/content/cross-app-sso.mdx +26 -0
- package/package.json +1 -1
- package/src/templates/workspace-core/.agents/skills/address-feedback/SKILL.md +25 -9
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { defineAction } from "@agent-native/core";
|
|
2
2
|
import { assertAccess } from "@agent-native/core/sharing";
|
|
3
|
-
import { and, asc, eq, inArray, ne } from "drizzle-orm";
|
|
3
|
+
import { and, asc, eq, inArray, isNull, ne, sql } from "drizzle-orm";
|
|
4
4
|
import { z } from "zod";
|
|
5
5
|
|
|
6
6
|
import { getDb, schema } from "../server/db/index.js";
|
|
@@ -157,41 +157,77 @@ async function clearSourceFederation(sourceId: string, now: string) {
|
|
|
157
157
|
});
|
|
158
158
|
}
|
|
159
159
|
|
|
160
|
-
async function removeRowsOwnedOnlyBySource(args: {
|
|
160
|
+
export async function removeRowsOwnedOnlyBySource(args: {
|
|
161
161
|
databaseId: string;
|
|
162
162
|
sourceId: string;
|
|
163
163
|
}) {
|
|
164
164
|
const db = getDb();
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
.
|
|
168
|
-
.
|
|
169
|
-
.where(
|
|
170
|
-
|
|
165
|
+
await db.transaction(async (tx) => {
|
|
166
|
+
const [lockedDatabase] = await tx
|
|
167
|
+
.update(schema.contentDatabases)
|
|
168
|
+
.set({ updatedAt: sql`${schema.contentDatabases.updatedAt}` })
|
|
169
|
+
.where(
|
|
170
|
+
and(
|
|
171
|
+
eq(schema.contentDatabases.id, args.databaseId),
|
|
172
|
+
isNull(schema.contentDatabases.deletedAt),
|
|
173
|
+
),
|
|
174
|
+
)
|
|
175
|
+
.returning({ id: schema.contentDatabases.id });
|
|
176
|
+
if (!lockedDatabase) throw new Error("Database is no longer active.");
|
|
177
|
+
|
|
178
|
+
const targetRows = await tx
|
|
171
179
|
.select()
|
|
172
180
|
.from(schema.contentDatabaseSourceRows)
|
|
173
|
-
.where(
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
181
|
+
.where(eq(schema.contentDatabaseSourceRows.sourceId, args.sourceId));
|
|
182
|
+
const siblingSources = await tx
|
|
183
|
+
.select({ id: schema.contentDatabaseSources.id })
|
|
184
|
+
.from(schema.contentDatabaseSources)
|
|
185
|
+
.where(
|
|
186
|
+
and(
|
|
187
|
+
eq(schema.contentDatabaseSources.databaseId, args.databaseId),
|
|
188
|
+
ne(schema.contentDatabaseSources.id, args.sourceId),
|
|
189
|
+
),
|
|
190
|
+
);
|
|
191
|
+
const otherRows = siblingSources.length
|
|
192
|
+
? await tx
|
|
193
|
+
.select()
|
|
194
|
+
.from(schema.contentDatabaseSourceRows)
|
|
195
|
+
.where(
|
|
196
|
+
inArray(
|
|
197
|
+
schema.contentDatabaseSourceRows.sourceId,
|
|
198
|
+
siblingSources.map((source) => source.id),
|
|
199
|
+
),
|
|
200
|
+
)
|
|
201
|
+
: [];
|
|
202
|
+
const otherDocumentIds = new Set(
|
|
203
|
+
otherRows.map((row) => row.documentId).filter(Boolean),
|
|
194
204
|
);
|
|
205
|
+
const itemIds = targetRows
|
|
206
|
+
.filter(
|
|
207
|
+
(row) =>
|
|
208
|
+
row.databaseItemId &&
|
|
209
|
+
row.documentId &&
|
|
210
|
+
!otherDocumentIds.has(row.documentId),
|
|
211
|
+
)
|
|
212
|
+
.map((row) => row.databaseItemId as string);
|
|
213
|
+
if (itemIds.length === 0) return;
|
|
214
|
+
await tx
|
|
215
|
+
.delete(schema.contentDatabaseItemKeyClaims)
|
|
216
|
+
.where(
|
|
217
|
+
and(
|
|
218
|
+
eq(schema.contentDatabaseItemKeyClaims.databaseId, args.databaseId),
|
|
219
|
+
inArray(schema.contentDatabaseItemKeyClaims.itemId, itemIds),
|
|
220
|
+
),
|
|
221
|
+
);
|
|
222
|
+
await tx
|
|
223
|
+
.delete(schema.contentDatabaseItems)
|
|
224
|
+
.where(
|
|
225
|
+
and(
|
|
226
|
+
eq(schema.contentDatabaseItems.databaseId, args.databaseId),
|
|
227
|
+
inArray(schema.contentDatabaseItems.id, itemIds),
|
|
228
|
+
),
|
|
229
|
+
);
|
|
230
|
+
});
|
|
195
231
|
}
|
|
196
232
|
|
|
197
233
|
export async function readBuilderCmsEntriesForRoleChange(
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { defineAction } from "@agent-native/core";
|
|
2
2
|
import { writeAppState } from "@agent-native/core/application-state";
|
|
3
3
|
import { assertAccess } from "@agent-native/core/sharing";
|
|
4
|
-
import { and, eq, sql } from "drizzle-orm";
|
|
4
|
+
import { and, eq, isNull, sql } from "drizzle-orm";
|
|
5
5
|
import { z } from "zod";
|
|
6
6
|
|
|
7
7
|
import { getDb, schema } from "../server/db/index.js";
|
|
@@ -114,12 +114,13 @@ export default defineAction({
|
|
|
114
114
|
}
|
|
115
115
|
|
|
116
116
|
if (args.id) {
|
|
117
|
+
const propertyId = args.id;
|
|
117
118
|
const [existing] = await db
|
|
118
119
|
.select()
|
|
119
120
|
.from(schema.documentPropertyDefinitions)
|
|
120
121
|
.where(
|
|
121
122
|
and(
|
|
122
|
-
eq(schema.documentPropertyDefinitions.id,
|
|
123
|
+
eq(schema.documentPropertyDefinitions.id, propertyId),
|
|
123
124
|
eq(
|
|
124
125
|
schema.documentPropertyDefinitions.ownerEmail,
|
|
125
126
|
document.ownerEmail,
|
|
@@ -127,7 +128,7 @@ export default defineAction({
|
|
|
127
128
|
eq(schema.documentPropertyDefinitions.databaseId, database.id),
|
|
128
129
|
),
|
|
129
130
|
);
|
|
130
|
-
if (!existing) throw new Error(`Property "${
|
|
131
|
+
if (!existing) throw new Error(`Property "${propertyId}" not found`);
|
|
131
132
|
if (existing.systemRole) {
|
|
132
133
|
throw new Error("System properties cannot be changed.");
|
|
133
134
|
}
|
|
@@ -158,42 +159,122 @@ export default defineAction({
|
|
|
158
159
|
optionsJson = serializePropertyOptions({ blocks: { primary: true } });
|
|
159
160
|
}
|
|
160
161
|
|
|
161
|
-
|
|
162
|
-
await
|
|
163
|
-
.
|
|
162
|
+
await db.transaction(async (tx) => {
|
|
163
|
+
const [lockedDatabase] = await tx
|
|
164
|
+
.update(schema.contentDatabases)
|
|
165
|
+
.set({ updatedAt: sql`${schema.contentDatabases.updatedAt}` })
|
|
164
166
|
.where(
|
|
165
167
|
and(
|
|
166
|
-
eq(schema.
|
|
167
|
-
eq(schema.
|
|
168
|
+
eq(schema.contentDatabases.id, database.id),
|
|
169
|
+
eq(schema.contentDatabases.documentId, database.documentId),
|
|
170
|
+
eq(schema.contentDatabases.ownerEmail, document.ownerEmail),
|
|
171
|
+
isNull(schema.contentDatabases.deletedAt),
|
|
168
172
|
),
|
|
169
|
-
)
|
|
170
|
-
|
|
173
|
+
)
|
|
174
|
+
.returning({ id: schema.contentDatabases.id });
|
|
175
|
+
if (!lockedDatabase) throw new Error("Database is no longer active.");
|
|
176
|
+
const [lockedExisting] = await tx
|
|
177
|
+
.update(schema.documentPropertyDefinitions)
|
|
178
|
+
.set({
|
|
179
|
+
updatedAt: sql`${schema.documentPropertyDefinitions.updatedAt}`,
|
|
180
|
+
})
|
|
181
|
+
.where(
|
|
182
|
+
and(
|
|
183
|
+
eq(schema.documentPropertyDefinitions.id, propertyId),
|
|
184
|
+
eq(
|
|
185
|
+
schema.documentPropertyDefinitions.ownerEmail,
|
|
186
|
+
document.ownerEmail,
|
|
187
|
+
),
|
|
188
|
+
eq(schema.documentPropertyDefinitions.databaseId, database.id),
|
|
189
|
+
),
|
|
190
|
+
)
|
|
191
|
+
.returning();
|
|
192
|
+
if (!lockedExisting)
|
|
193
|
+
throw new Error(`Property "${propertyId}" not found`);
|
|
194
|
+
if (lockedExisting.systemRole)
|
|
195
|
+
throw new Error("System properties cannot be changed.");
|
|
171
196
|
if (
|
|
172
|
-
|
|
173
|
-
|
|
197
|
+
isComputedPropertyType(lockedExisting.type as DocumentPropertyType) &&
|
|
198
|
+
lockedExisting.type !== type
|
|
174
199
|
) {
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
200
|
+
throw new Error("Computed property types cannot be changed.");
|
|
201
|
+
}
|
|
202
|
+
const lockedIsPrimaryBlocks =
|
|
203
|
+
isBlocksPropertyType(lockedExisting.type as DocumentPropertyType) &&
|
|
204
|
+
isPrimaryBlocksField(
|
|
205
|
+
parsePropertyOptions(lockedExisting.optionsJson),
|
|
206
|
+
);
|
|
207
|
+
if (lockedIsPrimaryBlocks && lockedExisting.type !== type) {
|
|
208
|
+
throw new Error(
|
|
209
|
+
"The primary Content (Blocks) field cannot change type. Delete it from the database view to remove the body.",
|
|
210
|
+
);
|
|
178
211
|
}
|
|
179
|
-
}
|
|
180
212
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
213
|
+
if (lockedExisting.type !== type) {
|
|
214
|
+
const [mappedSourceField] = await tx
|
|
215
|
+
.select({ id: schema.contentDatabaseSourceFields.id })
|
|
216
|
+
.from(schema.contentDatabaseSourceFields)
|
|
217
|
+
.where(
|
|
218
|
+
eq(schema.contentDatabaseSourceFields.propertyId, propertyId),
|
|
219
|
+
)
|
|
220
|
+
.limit(1);
|
|
221
|
+
if (mappedSourceField) {
|
|
222
|
+
throw new Error(
|
|
223
|
+
"A property bound to a source field must be unbound before changing its type.",
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
await tx
|
|
227
|
+
.delete(schema.documentPropertyValues)
|
|
228
|
+
.where(
|
|
229
|
+
and(
|
|
230
|
+
eq(schema.documentPropertyValues.propertyId, propertyId),
|
|
231
|
+
eq(
|
|
232
|
+
schema.documentPropertyValues.ownerEmail,
|
|
233
|
+
document.ownerEmail,
|
|
234
|
+
),
|
|
235
|
+
),
|
|
236
|
+
);
|
|
237
|
+
await tx
|
|
238
|
+
.delete(schema.contentDatabaseItemKeyClaims)
|
|
239
|
+
.where(
|
|
240
|
+
and(
|
|
241
|
+
eq(schema.contentDatabaseItemKeyClaims.databaseId, database.id),
|
|
242
|
+
eq(schema.contentDatabaseItemKeyClaims.propertyId, propertyId),
|
|
243
|
+
),
|
|
244
|
+
);
|
|
245
|
+
// Switching a Blocks field to another type drops its independent content.
|
|
246
|
+
if (
|
|
247
|
+
isBlocksPropertyType(lockedExisting.type as DocumentPropertyType) &&
|
|
248
|
+
!isBlocksPropertyType(type)
|
|
249
|
+
) {
|
|
250
|
+
await tx
|
|
251
|
+
.delete(schema.documentBlockFieldContents)
|
|
252
|
+
.where(
|
|
253
|
+
eq(schema.documentBlockFieldContents.propertyId, propertyId),
|
|
254
|
+
);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
await tx
|
|
259
|
+
.update(schema.documentPropertyDefinitions)
|
|
260
|
+
.set({
|
|
261
|
+
name,
|
|
262
|
+
...(args.description === undefined
|
|
263
|
+
? {}
|
|
264
|
+
: { description: args.description.trim() }),
|
|
265
|
+
type,
|
|
266
|
+
visibility:
|
|
267
|
+
args.visibility === undefined
|
|
268
|
+
? normalizePropertyVisibility(lockedExisting.visibility)
|
|
269
|
+
: normalizePropertyVisibility(args.visibility),
|
|
270
|
+
optionsJson:
|
|
271
|
+
lockedIsPrimaryBlocks && isBlocksPropertyType(type)
|
|
272
|
+
? serializePropertyOptions({ blocks: { primary: true } })
|
|
273
|
+
: optionsJson,
|
|
274
|
+
updatedAt: now,
|
|
275
|
+
})
|
|
276
|
+
.where(eq(schema.documentPropertyDefinitions.id, propertyId));
|
|
277
|
+
});
|
|
197
278
|
} else {
|
|
198
279
|
await withPositionLock(
|
|
199
280
|
propertyDefinitionsPositionScope(database.id),
|
|
@@ -21,13 +21,11 @@ export default defineAction({
|
|
|
21
21
|
await assertAccess("document", database.documentId, "admin");
|
|
22
22
|
const db = getDb();
|
|
23
23
|
const deletedAt = database.deletedAt ?? new Date().toISOString();
|
|
24
|
-
await
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
deletedAt,
|
|
30
|
-
),
|
|
24
|
+
await trashDocumentSubtree(
|
|
25
|
+
db,
|
|
26
|
+
database.documentId,
|
|
27
|
+
database.ownerEmail,
|
|
28
|
+
deletedAt,
|
|
31
29
|
);
|
|
32
30
|
|
|
33
31
|
await writeAppState("refresh-signal", { ts: Date.now() });
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { defineAction } from "@agent-native/core";
|
|
2
2
|
import { writeAppState } from "@agent-native/core/application-state";
|
|
3
3
|
import { assertAccess } from "@agent-native/core/sharing";
|
|
4
|
-
import { and, eq, inArray } from "drizzle-orm";
|
|
4
|
+
import { and, eq, inArray, isNull, sql } from "drizzle-orm";
|
|
5
5
|
import { z } from "zod";
|
|
6
6
|
|
|
7
7
|
import { getDb, schema } from "../server/db/index.js";
|
|
@@ -65,74 +65,121 @@ export default defineAction({
|
|
|
65
65
|
isBlocks &&
|
|
66
66
|
isPrimaryBlocksField(parsePropertyOptions(definition.optionsJson));
|
|
67
67
|
|
|
68
|
-
await db
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
68
|
+
await db.transaction(async (tx) => {
|
|
69
|
+
const [lockedDatabase] = await tx
|
|
70
|
+
.update(schema.contentDatabases)
|
|
71
|
+
.set({ updatedAt: sql`${schema.contentDatabases.updatedAt}` })
|
|
72
|
+
.where(
|
|
73
|
+
and(
|
|
74
|
+
eq(schema.contentDatabases.id, database.id),
|
|
75
|
+
eq(schema.contentDatabases.documentId, database.documentId),
|
|
76
|
+
eq(schema.contentDatabases.ownerEmail, document.ownerEmail),
|
|
77
|
+
isNull(schema.contentDatabases.deletedAt),
|
|
78
|
+
),
|
|
79
|
+
)
|
|
80
|
+
.returning({ id: schema.contentDatabases.id });
|
|
81
|
+
if (!lockedDatabase) throw new Error("Database is no longer active.");
|
|
82
|
+
const [lockedDefinition] = await tx
|
|
83
|
+
.update(schema.documentPropertyDefinitions)
|
|
84
|
+
.set({
|
|
85
|
+
updatedAt: sql`${schema.documentPropertyDefinitions.updatedAt}`,
|
|
86
|
+
})
|
|
87
|
+
.where(
|
|
88
|
+
and(
|
|
89
|
+
eq(schema.documentPropertyDefinitions.id, propertyId),
|
|
90
|
+
eq(
|
|
91
|
+
schema.documentPropertyDefinitions.ownerEmail,
|
|
92
|
+
document.ownerEmail,
|
|
93
|
+
),
|
|
94
|
+
eq(schema.documentPropertyDefinitions.databaseId, database.id),
|
|
95
|
+
),
|
|
96
|
+
)
|
|
97
|
+
.returning({
|
|
98
|
+
id: schema.documentPropertyDefinitions.id,
|
|
99
|
+
systemRole: schema.documentPropertyDefinitions.systemRole,
|
|
100
|
+
});
|
|
101
|
+
if (!lockedDefinition)
|
|
102
|
+
throw new Error(`Property "${propertyId}" not found`);
|
|
103
|
+
if (lockedDefinition.systemRole)
|
|
104
|
+
throw new Error("System properties cannot be deleted.");
|
|
105
|
+
|
|
106
|
+
await tx
|
|
107
|
+
.delete(schema.documentPropertyValues)
|
|
108
|
+
.where(eq(schema.documentPropertyValues.propertyId, propertyId));
|
|
109
|
+
await tx
|
|
110
|
+
.delete(schema.contentDatabaseItemKeyClaims)
|
|
111
|
+
.where(
|
|
112
|
+
and(
|
|
113
|
+
eq(schema.contentDatabaseItemKeyClaims.databaseId, database.id),
|
|
114
|
+
eq(schema.contentDatabaseItemKeyClaims.propertyId, propertyId),
|
|
115
|
+
),
|
|
116
|
+
);
|
|
117
|
+
await tx
|
|
118
|
+
.delete(schema.documentPropertyDefinitions)
|
|
119
|
+
.where(eq(schema.documentPropertyDefinitions.id, propertyId));
|
|
74
120
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
121
|
+
if (isBlocks) {
|
|
122
|
+
// Drop the independent content for this Blocks field across every row.
|
|
123
|
+
await tx
|
|
124
|
+
.delete(schema.documentBlockFieldContents)
|
|
125
|
+
.where(eq(schema.documentBlockFieldContents.propertyId, propertyId));
|
|
80
126
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
127
|
+
// Deleting the primary "Content" field removes the body (documents.content)
|
|
128
|
+
// for every object of this type, per the delete warning shown in the UI.
|
|
129
|
+
if (isPrimaryBlocks) {
|
|
130
|
+
// Record that the primary was intentionally removed: clear the single
|
|
131
|
+
// source of truth but LEAVE blocks_seeded = 1, so neither the read path
|
|
132
|
+
// nor the startup repair ever recreates it. Deleting the only Blocks
|
|
133
|
+
// field is an allowed product action that leaves the row metadata-only
|
|
134
|
+
// with ZERO Blocks fields.
|
|
135
|
+
await tx
|
|
136
|
+
.update(schema.contentDatabases)
|
|
137
|
+
.set({
|
|
138
|
+
primaryBlocksPropertyId: null,
|
|
139
|
+
updatedAt: new Date().toISOString(),
|
|
140
|
+
})
|
|
141
|
+
.where(eq(schema.contentDatabases.id, database.id));
|
|
96
142
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
143
|
+
const items = await tx
|
|
144
|
+
.select({ documentId: schema.contentDatabaseItems.documentId })
|
|
145
|
+
.from(schema.contentDatabaseItems)
|
|
146
|
+
.where(eq(schema.contentDatabaseItems.databaseId, database.id));
|
|
147
|
+
const documentIds = items.map((item) => item.documentId);
|
|
148
|
+
if (documentIds.length > 0) {
|
|
149
|
+
const now = new Date().toISOString();
|
|
150
|
+
await tx
|
|
151
|
+
.update(schema.documents)
|
|
152
|
+
.set({ content: "", updatedAt: now })
|
|
153
|
+
.where(inArray(schema.documents.id, documentIds));
|
|
154
|
+
}
|
|
108
155
|
}
|
|
109
156
|
}
|
|
110
|
-
}
|
|
111
157
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
158
|
+
// Free any source field that was mapped to this property so it returns to
|
|
159
|
+
// the "From source" picker immediately, instead of staying orphaned until
|
|
160
|
+
// the next source refresh reconciles it.
|
|
161
|
+
const mappedFields = await tx
|
|
162
|
+
.select({
|
|
163
|
+
id: schema.contentDatabaseSourceFields.id,
|
|
164
|
+
sourceFieldKey: schema.contentDatabaseSourceFields.sourceFieldKey,
|
|
165
|
+
})
|
|
166
|
+
.from(schema.contentDatabaseSourceFields)
|
|
167
|
+
.where(eq(schema.contentDatabaseSourceFields.propertyId, propertyId));
|
|
168
|
+
if (mappedFields.length > 0) {
|
|
169
|
+
const now = new Date().toISOString();
|
|
170
|
+
for (const mapped of mappedFields) {
|
|
171
|
+
await tx
|
|
172
|
+
.update(schema.contentDatabaseSourceFields)
|
|
173
|
+
.set({
|
|
174
|
+
propertyId: null,
|
|
175
|
+
localFieldKey: mapped.sourceFieldKey,
|
|
176
|
+
mappingType: "property",
|
|
177
|
+
updatedAt: now,
|
|
178
|
+
})
|
|
179
|
+
.where(eq(schema.contentDatabaseSourceFields.id, mapped.id));
|
|
180
|
+
}
|
|
134
181
|
}
|
|
135
|
-
}
|
|
182
|
+
});
|
|
136
183
|
|
|
137
184
|
await writeAppState("refresh-signal", { ts: Date.now() });
|
|
138
185
|
|