@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,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, isNotNull, isNull, ne, or } from "drizzle-orm";
|
|
4
|
+
import { and, eq, inArray, isNotNull, isNull, ne, or, sql } from "drizzle-orm";
|
|
5
5
|
import { z } from "zod";
|
|
6
6
|
|
|
7
7
|
import { getDb, schema } from "../server/db/index.js";
|
|
@@ -97,6 +97,28 @@ async function selectDatabaseItemDocuments(
|
|
|
97
97
|
return ownedRows.map((row) => ({ documentId: row.id }));
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
+
async function selectMembershipDatabaseIds(
|
|
101
|
+
db: ReturnType<typeof getDb>,
|
|
102
|
+
documentIds: string[],
|
|
103
|
+
ownerEmail: string,
|
|
104
|
+
) {
|
|
105
|
+
const rows: Array<{ databaseId: string }> = [];
|
|
106
|
+
for (const batch of chunks(documentIds, DELETE_BATCH_SIZE)) {
|
|
107
|
+
rows.push(
|
|
108
|
+
...(await db
|
|
109
|
+
.select({ databaseId: schema.contentDatabaseItems.databaseId })
|
|
110
|
+
.from(schema.contentDatabaseItems)
|
|
111
|
+
.where(
|
|
112
|
+
and(
|
|
113
|
+
inArray(schema.contentDatabaseItems.documentId, batch),
|
|
114
|
+
eq(schema.contentDatabaseItems.ownerEmail, ownerEmail),
|
|
115
|
+
),
|
|
116
|
+
)),
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
return [...new Set(rows.map((row) => row.databaseId))];
|
|
120
|
+
}
|
|
121
|
+
|
|
100
122
|
async function collectDocumentSubtreeForDelete(
|
|
101
123
|
db: ReturnType<typeof getDb>,
|
|
102
124
|
rootId: string,
|
|
@@ -150,22 +172,83 @@ async function collectDocumentSubtreeForDelete(
|
|
|
150
172
|
frontier = [...next];
|
|
151
173
|
}
|
|
152
174
|
|
|
175
|
+
const collectedDocumentIds = [...documentIds];
|
|
176
|
+
const collectedOwnedDatabaseIds = [...ownedDatabaseIds];
|
|
153
177
|
return {
|
|
154
|
-
documentIds:
|
|
155
|
-
ownedDatabaseIds:
|
|
178
|
+
documentIds: collectedDocumentIds,
|
|
179
|
+
ownedDatabaseIds: collectedOwnedDatabaseIds,
|
|
180
|
+
lockDatabaseIds: [
|
|
181
|
+
...new Set([
|
|
182
|
+
...collectedOwnedDatabaseIds,
|
|
183
|
+
...(await selectMembershipDatabaseIds(
|
|
184
|
+
db,
|
|
185
|
+
collectedDocumentIds,
|
|
186
|
+
ownerEmail,
|
|
187
|
+
)),
|
|
188
|
+
]),
|
|
189
|
+
],
|
|
156
190
|
};
|
|
157
191
|
}
|
|
158
192
|
|
|
193
|
+
async function lockDatabasesAndRecollect<
|
|
194
|
+
T extends { lockDatabaseIds: string[] },
|
|
195
|
+
>(
|
|
196
|
+
db: ReturnType<typeof getDb>,
|
|
197
|
+
ownerEmail: string,
|
|
198
|
+
collect: () => Promise<T>,
|
|
199
|
+
): Promise<T> {
|
|
200
|
+
const lockedDatabaseIds = new Set<string>();
|
|
201
|
+
let collected = await collect();
|
|
202
|
+
while (true) {
|
|
203
|
+
const unlockedDatabaseIds = collected.lockDatabaseIds.filter(
|
|
204
|
+
(databaseId) => !lockedDatabaseIds.has(databaseId),
|
|
205
|
+
);
|
|
206
|
+
if (unlockedDatabaseIds.length === 0) return collected;
|
|
207
|
+
for (const batch of chunks(unlockedDatabaseIds, DELETE_BATCH_SIZE)) {
|
|
208
|
+
await db
|
|
209
|
+
.update(schema.contentDatabases)
|
|
210
|
+
.set({ updatedAt: sql`${schema.contentDatabases.updatedAt}` })
|
|
211
|
+
.where(
|
|
212
|
+
and(
|
|
213
|
+
inArray(schema.contentDatabases.id, batch),
|
|
214
|
+
eq(schema.contentDatabases.ownerEmail, ownerEmail),
|
|
215
|
+
),
|
|
216
|
+
);
|
|
217
|
+
for (const databaseId of batch) lockedDatabaseIds.add(databaseId);
|
|
218
|
+
}
|
|
219
|
+
collected = await collect();
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
159
223
|
export async function trashDocumentSubtree(
|
|
160
224
|
db: ReturnType<typeof getDb>,
|
|
161
225
|
id: string,
|
|
162
226
|
ownerEmail: string,
|
|
163
227
|
trashedAt = new Date().toISOString(),
|
|
164
228
|
): Promise<string[]> {
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
229
|
+
return db.transaction((tx) =>
|
|
230
|
+
trashDocumentSubtreeInTransaction(
|
|
231
|
+
tx as unknown as ReturnType<typeof getDb>,
|
|
232
|
+
id,
|
|
233
|
+
ownerEmail,
|
|
234
|
+
trashedAt,
|
|
235
|
+
),
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
async function trashDocumentSubtreeInTransaction(
|
|
240
|
+
db: ReturnType<typeof getDb>,
|
|
241
|
+
id: string,
|
|
242
|
+
ownerEmail: string,
|
|
243
|
+
trashedAt: string,
|
|
244
|
+
): Promise<string[]> {
|
|
245
|
+
// Stable-key upserts lock their canonical database row before creating or
|
|
246
|
+
// updating children. Acquire the same locks, then collect again: an upsert
|
|
247
|
+
// that won the lock before this transaction may have added a child after the
|
|
248
|
+
// first traversal, while one that arrives later must wait until the database
|
|
249
|
+
// has been marked deleted and will fail closed.
|
|
250
|
+
const { documentIds } = await lockDatabasesAndRecollect(db, ownerEmail, () =>
|
|
251
|
+
collectDocumentSubtreeForDelete(db, id, ownerEmail),
|
|
169
252
|
);
|
|
170
253
|
await assertNotWorkspaceCatalogDocuments(db, documentIds, "deleted");
|
|
171
254
|
|
|
@@ -316,14 +399,20 @@ export async function deleteDocumentRecursive(
|
|
|
316
399
|
id: string,
|
|
317
400
|
ownerEmail: string,
|
|
318
401
|
): Promise<string[]> {
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
402
|
+
return db.transaction(async (tx) => {
|
|
403
|
+
const scopedDb = tx as unknown as ReturnType<typeof getDb>;
|
|
404
|
+
const { documentIds, ownedDatabaseIds } = await lockDatabasesAndRecollect(
|
|
405
|
+
scopedDb,
|
|
406
|
+
ownerEmail,
|
|
407
|
+
() => collectDocumentSubtreeForDelete(scopedDb, id, ownerEmail),
|
|
408
|
+
);
|
|
409
|
+
return deleteCollectedDocuments(
|
|
410
|
+
scopedDb,
|
|
411
|
+
documentIds,
|
|
412
|
+
ownedDatabaseIds,
|
|
413
|
+
ownerEmail,
|
|
414
|
+
);
|
|
415
|
+
});
|
|
327
416
|
}
|
|
328
417
|
|
|
329
418
|
async function deleteCollectedDocuments(
|
|
@@ -416,6 +505,27 @@ async function deleteCollectedDocuments(
|
|
|
416
505
|
);
|
|
417
506
|
});
|
|
418
507
|
|
|
508
|
+
await deleteWhereIn(ownedDatabaseIds, async (databaseIdBatch) => {
|
|
509
|
+
await db
|
|
510
|
+
.delete(schema.contentDatabaseItemKeyClaims)
|
|
511
|
+
.where(
|
|
512
|
+
inArray(
|
|
513
|
+
schema.contentDatabaseItemKeyClaims.databaseId,
|
|
514
|
+
databaseIdBatch,
|
|
515
|
+
),
|
|
516
|
+
);
|
|
517
|
+
});
|
|
518
|
+
await deleteWhereIn(documentIds, async (documentIdBatch) => {
|
|
519
|
+
await db
|
|
520
|
+
.delete(schema.contentDatabaseItemKeyClaims)
|
|
521
|
+
.where(
|
|
522
|
+
inArray(
|
|
523
|
+
schema.contentDatabaseItemKeyClaims.documentId,
|
|
524
|
+
documentIdBatch,
|
|
525
|
+
),
|
|
526
|
+
);
|
|
527
|
+
});
|
|
528
|
+
|
|
419
529
|
await deleteWhereIn(documentIds, async (documentIdBatch) => {
|
|
420
530
|
await db
|
|
421
531
|
.delete(schema.contentDatabaseBodyHydrationQueue)
|
|
@@ -518,6 +628,20 @@ export async function deleteTrashedDocumentSubtree(
|
|
|
518
628
|
db: ReturnType<typeof getDb>,
|
|
519
629
|
id: string,
|
|
520
630
|
ownerEmail: string,
|
|
631
|
+
): Promise<string[]> {
|
|
632
|
+
return db.transaction((tx) =>
|
|
633
|
+
deleteTrashedDocumentSubtreeInTransaction(
|
|
634
|
+
tx as unknown as ReturnType<typeof getDb>,
|
|
635
|
+
id,
|
|
636
|
+
ownerEmail,
|
|
637
|
+
),
|
|
638
|
+
);
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
async function deleteTrashedDocumentSubtreeInTransaction(
|
|
642
|
+
db: ReturnType<typeof getDb>,
|
|
643
|
+
id: string,
|
|
644
|
+
ownerEmail: string,
|
|
521
645
|
): Promise<string[]> {
|
|
522
646
|
const [root] = await db
|
|
523
647
|
.select({ id: schema.documents.id })
|
|
@@ -537,23 +661,43 @@ export async function deleteTrashedDocumentSubtree(
|
|
|
537
661
|
);
|
|
538
662
|
}
|
|
539
663
|
|
|
540
|
-
const documentIds = (
|
|
541
|
-
await db
|
|
542
|
-
.select({ id: schema.documents.id })
|
|
543
|
-
.from(schema.documents)
|
|
544
|
-
.where(
|
|
545
|
-
and(
|
|
546
|
-
eq(schema.documents.ownerEmail, ownerEmail),
|
|
547
|
-
eq(schema.documents.trashRootId, id),
|
|
548
|
-
isNotNull(schema.documents.trashedAt),
|
|
549
|
-
),
|
|
550
|
-
)
|
|
551
|
-
).map((document) => document.id);
|
|
552
|
-
const ownedDatabaseIds = await selectOwnedDatabaseIds(
|
|
664
|
+
const { documentIds, ownedDatabaseIds } = await lockDatabasesAndRecollect(
|
|
553
665
|
db,
|
|
554
|
-
documentIds,
|
|
555
666
|
ownerEmail,
|
|
556
|
-
|
|
667
|
+
async () => {
|
|
668
|
+
const collectedDocumentIds = (
|
|
669
|
+
await db
|
|
670
|
+
.select({ id: schema.documents.id })
|
|
671
|
+
.from(schema.documents)
|
|
672
|
+
.where(
|
|
673
|
+
and(
|
|
674
|
+
eq(schema.documents.ownerEmail, ownerEmail),
|
|
675
|
+
eq(schema.documents.trashRootId, id),
|
|
676
|
+
isNotNull(schema.documents.trashedAt),
|
|
677
|
+
),
|
|
678
|
+
)
|
|
679
|
+
).map((document) => document.id);
|
|
680
|
+
const collectedDatabaseIds = await selectOwnedDatabaseIds(
|
|
681
|
+
db,
|
|
682
|
+
collectedDocumentIds,
|
|
683
|
+
ownerEmail,
|
|
684
|
+
).then((rows) => rows.map((database) => database.id));
|
|
685
|
+
return {
|
|
686
|
+
documentIds: collectedDocumentIds,
|
|
687
|
+
ownedDatabaseIds: collectedDatabaseIds,
|
|
688
|
+
lockDatabaseIds: [
|
|
689
|
+
...new Set([
|
|
690
|
+
...collectedDatabaseIds,
|
|
691
|
+
...(await selectMembershipDatabaseIds(
|
|
692
|
+
db,
|
|
693
|
+
collectedDocumentIds,
|
|
694
|
+
ownerEmail,
|
|
695
|
+
)),
|
|
696
|
+
]),
|
|
697
|
+
],
|
|
698
|
+
};
|
|
699
|
+
},
|
|
700
|
+
);
|
|
557
701
|
|
|
558
702
|
await db
|
|
559
703
|
.update(schema.documents)
|
|
@@ -633,12 +777,10 @@ export default defineAction({
|
|
|
633
777
|
if (systemDatabase?.systemRole) {
|
|
634
778
|
throw new Error("System Content database documents cannot be deleted");
|
|
635
779
|
}
|
|
636
|
-
const deleted = await
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
existing.ownerEmail as string,
|
|
641
|
-
),
|
|
780
|
+
const deleted = await trashDocumentSubtree(
|
|
781
|
+
db,
|
|
782
|
+
id,
|
|
783
|
+
existing.ownerEmail as string,
|
|
642
784
|
);
|
|
643
785
|
|
|
644
786
|
await writeAppState("refresh-signal", { ts: Date.now() });
|
|
@@ -88,6 +88,33 @@ export default defineAction({
|
|
|
88
88
|
.where(eq(schema.documentShares.resourceId, row.database.documentId));
|
|
89
89
|
|
|
90
90
|
await db.transaction(async (tx) => {
|
|
91
|
+
const [lockedDatabase] = await tx
|
|
92
|
+
.update(schema.contentDatabases)
|
|
93
|
+
.set({ updatedAt: sql`${schema.contentDatabases.updatedAt}` })
|
|
94
|
+
.where(
|
|
95
|
+
and(
|
|
96
|
+
eq(schema.contentDatabases.id, row.database.id),
|
|
97
|
+
eq(schema.contentDatabases.ownerEmail, row.database.ownerEmail),
|
|
98
|
+
isNull(schema.contentDatabases.deletedAt),
|
|
99
|
+
),
|
|
100
|
+
)
|
|
101
|
+
.returning({ id: schema.contentDatabases.id });
|
|
102
|
+
if (!lockedDatabase) throw new Error("Database is no longer active.");
|
|
103
|
+
const [claimedSource] = await tx
|
|
104
|
+
.select({ id: schema.contentDatabaseItemKeyClaims.id })
|
|
105
|
+
.from(schema.contentDatabaseItemKeyClaims)
|
|
106
|
+
.where(
|
|
107
|
+
and(
|
|
108
|
+
eq(schema.contentDatabaseItemKeyClaims.databaseId, row.database.id),
|
|
109
|
+
eq(schema.contentDatabaseItemKeyClaims.documentId, row.document.id),
|
|
110
|
+
),
|
|
111
|
+
)
|
|
112
|
+
.limit(1);
|
|
113
|
+
if (claimedSource) {
|
|
114
|
+
throw new Error(
|
|
115
|
+
"Rows with active stable-key claims cannot be duplicated.",
|
|
116
|
+
);
|
|
117
|
+
}
|
|
91
118
|
await tx
|
|
92
119
|
.update(schema.contentDatabaseItems)
|
|
93
120
|
.set({
|
|
@@ -2,7 +2,7 @@ import { defineAction } from "@agent-native/core";
|
|
|
2
2
|
import { writeAppState } from "@agent-native/core/application-state";
|
|
3
3
|
import { getRequestUserEmail } from "@agent-native/core/server/request-context";
|
|
4
4
|
import { assertAccess } from "@agent-native/core/sharing";
|
|
5
|
-
import { and, eq, gte, inArray, sql } from "drizzle-orm";
|
|
5
|
+
import { and, eq, gte, inArray, isNull, sql } from "drizzle-orm";
|
|
6
6
|
|
|
7
7
|
import { getDb, schema } from "../server/db/index.js";
|
|
8
8
|
import { ensureDocumentsFilesMembership } from "./_content-files.js";
|
|
@@ -86,6 +86,36 @@ export default defineAction({
|
|
|
86
86
|
}));
|
|
87
87
|
|
|
88
88
|
await db.transaction(async (tx) => {
|
|
89
|
+
const [lockedDatabase] = await tx
|
|
90
|
+
.update(schema.contentDatabases)
|
|
91
|
+
.set({ updatedAt: sql`${schema.contentDatabases.updatedAt}` })
|
|
92
|
+
.where(
|
|
93
|
+
and(
|
|
94
|
+
eq(schema.contentDatabases.id, database.id),
|
|
95
|
+
eq(schema.contentDatabases.ownerEmail, database.ownerEmail),
|
|
96
|
+
isNull(schema.contentDatabases.deletedAt),
|
|
97
|
+
),
|
|
98
|
+
)
|
|
99
|
+
.returning({ id: schema.contentDatabases.id });
|
|
100
|
+
if (!lockedDatabase) throw new Error("Database is no longer active.");
|
|
101
|
+
const [claimedSource] = await tx
|
|
102
|
+
.select({ id: schema.contentDatabaseItemKeyClaims.id })
|
|
103
|
+
.from(schema.contentDatabaseItemKeyClaims)
|
|
104
|
+
.where(
|
|
105
|
+
and(
|
|
106
|
+
eq(schema.contentDatabaseItemKeyClaims.databaseId, database.id),
|
|
107
|
+
inArray(
|
|
108
|
+
schema.contentDatabaseItemKeyClaims.documentId,
|
|
109
|
+
sourceDocumentIds,
|
|
110
|
+
),
|
|
111
|
+
),
|
|
112
|
+
)
|
|
113
|
+
.limit(1);
|
|
114
|
+
if (claimedSource) {
|
|
115
|
+
throw new Error(
|
|
116
|
+
"Rows with active stable-key claims cannot be duplicated.",
|
|
117
|
+
);
|
|
118
|
+
}
|
|
89
119
|
await tx
|
|
90
120
|
.update(schema.contentDatabaseItems)
|
|
91
121
|
.set({
|
|
@@ -15,12 +15,10 @@ export default defineAction({
|
|
|
15
15
|
run: async ({ id }) => {
|
|
16
16
|
const access = await assertAccess("document", id, "admin");
|
|
17
17
|
const db = getDb();
|
|
18
|
-
const deleted = await
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
access.resource.ownerEmail as string,
|
|
23
|
-
),
|
|
18
|
+
const deleted = await deleteTrashedDocumentSubtree(
|
|
19
|
+
db,
|
|
20
|
+
id,
|
|
21
|
+
access.resource.ownerEmail as string,
|
|
24
22
|
);
|
|
25
23
|
await writeAppState("refresh-signal", { ts: Date.now() });
|
|
26
24
|
return { success: true, deleted: deleted.length };
|
|
@@ -145,6 +145,19 @@ export default defineAction({
|
|
|
145
145
|
),
|
|
146
146
|
);
|
|
147
147
|
}
|
|
148
|
+
if (removedItemIds.length > 0) {
|
|
149
|
+
await tx
|
|
150
|
+
.delete(schema.contentDatabaseItemKeyClaims)
|
|
151
|
+
.where(
|
|
152
|
+
and(
|
|
153
|
+
eq(schema.contentDatabaseItemKeyClaims.databaseId, database.id),
|
|
154
|
+
inArray(
|
|
155
|
+
schema.contentDatabaseItemKeyClaims.itemId,
|
|
156
|
+
removedItemIds,
|
|
157
|
+
),
|
|
158
|
+
),
|
|
159
|
+
);
|
|
160
|
+
}
|
|
148
161
|
if (removedItemIds.length > 0) {
|
|
149
162
|
await tx
|
|
150
163
|
.delete(schema.contentDatabaseItems)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { defineAction } from "@agent-native/core";
|
|
2
2
|
import { assertAccess } from "@agent-native/core/sharing";
|
|
3
|
-
import { and, eq } from "drizzle-orm";
|
|
3
|
+
import { and, eq, isNull, ne, sql } from "drizzle-orm";
|
|
4
4
|
import { z } from "zod";
|
|
5
5
|
|
|
6
6
|
import { getDb, schema } from "../server/db/index.js";
|
|
@@ -130,7 +130,65 @@ export default defineAction({
|
|
|
130
130
|
|
|
131
131
|
const valueJson = normalizedValueJson(type, value);
|
|
132
132
|
await db.transaction(async (tx) => {
|
|
133
|
+
const [lockedDatabase] = await tx
|
|
134
|
+
.update(schema.contentDatabases)
|
|
135
|
+
.set({ updatedAt: sql`${schema.contentDatabases.updatedAt}` })
|
|
136
|
+
.where(
|
|
137
|
+
and(
|
|
138
|
+
eq(schema.contentDatabases.id, database.id),
|
|
139
|
+
eq(schema.contentDatabases.documentId, database.documentId),
|
|
140
|
+
eq(schema.contentDatabases.ownerEmail, database.ownerEmail),
|
|
141
|
+
isNull(schema.contentDatabases.deletedAt),
|
|
142
|
+
),
|
|
143
|
+
)
|
|
144
|
+
.returning({ id: schema.contentDatabases.id });
|
|
145
|
+
if (!lockedDatabase) throw new Error("Database is no longer active.");
|
|
146
|
+
const [lockedDefinition] = await tx
|
|
147
|
+
.update(schema.documentPropertyDefinitions)
|
|
148
|
+
.set({
|
|
149
|
+
updatedAt: sql`${schema.documentPropertyDefinitions.updatedAt}`,
|
|
150
|
+
})
|
|
151
|
+
.where(
|
|
152
|
+
and(
|
|
153
|
+
eq(schema.documentPropertyDefinitions.id, propertyId),
|
|
154
|
+
eq(schema.documentPropertyDefinitions.databaseId, database.id),
|
|
155
|
+
eq(
|
|
156
|
+
schema.documentPropertyDefinitions.ownerEmail,
|
|
157
|
+
database.ownerEmail,
|
|
158
|
+
),
|
|
159
|
+
),
|
|
160
|
+
)
|
|
161
|
+
.returning({
|
|
162
|
+
type: schema.documentPropertyDefinitions.type,
|
|
163
|
+
systemRole: schema.documentPropertyDefinitions.systemRole,
|
|
164
|
+
});
|
|
165
|
+
if (
|
|
166
|
+
!lockedDefinition ||
|
|
167
|
+
lockedDefinition.type !== definition.type ||
|
|
168
|
+
lockedDefinition.systemRole
|
|
169
|
+
) {
|
|
170
|
+
throw new Error(
|
|
171
|
+
`Property "${propertyId}" changed or was deleted before its value could be written.`,
|
|
172
|
+
);
|
|
173
|
+
}
|
|
133
174
|
await lockDatabaseMemberships(tx, [membership.id]);
|
|
175
|
+
const [conflictingClaim] = await tx
|
|
176
|
+
.select({ id: schema.contentDatabaseItemKeyClaims.id })
|
|
177
|
+
.from(schema.contentDatabaseItemKeyClaims)
|
|
178
|
+
.where(
|
|
179
|
+
and(
|
|
180
|
+
eq(schema.contentDatabaseItemKeyClaims.databaseId, database.id),
|
|
181
|
+
eq(schema.contentDatabaseItemKeyClaims.propertyId, propertyId),
|
|
182
|
+
eq(schema.contentDatabaseItemKeyClaims.keyValueJson, valueJson),
|
|
183
|
+
ne(schema.contentDatabaseItemKeyClaims.documentId, documentId),
|
|
184
|
+
),
|
|
185
|
+
)
|
|
186
|
+
.limit(1);
|
|
187
|
+
if (conflictingClaim) {
|
|
188
|
+
throw new Error(
|
|
189
|
+
"This value is already claimed as another row's stable key.",
|
|
190
|
+
);
|
|
191
|
+
}
|
|
134
192
|
const [existing] = await tx
|
|
135
193
|
.select({ id: schema.documentPropertyValues.id })
|
|
136
194
|
.from(schema.documentPropertyValues)
|
|
@@ -156,6 +214,16 @@ export default defineAction({
|
|
|
156
214
|
updatedAt: now,
|
|
157
215
|
});
|
|
158
216
|
}
|
|
217
|
+
await tx
|
|
218
|
+
.delete(schema.contentDatabaseItemKeyClaims)
|
|
219
|
+
.where(
|
|
220
|
+
and(
|
|
221
|
+
eq(schema.contentDatabaseItemKeyClaims.databaseId, database.id),
|
|
222
|
+
eq(schema.contentDatabaseItemKeyClaims.propertyId, propertyId),
|
|
223
|
+
eq(schema.contentDatabaseItemKeyClaims.documentId, documentId),
|
|
224
|
+
ne(schema.contentDatabaseItemKeyClaims.keyValueJson, valueJson),
|
|
225
|
+
),
|
|
226
|
+
);
|
|
159
227
|
});
|
|
160
228
|
|
|
161
229
|
return {
|