@hiai-gg/docsmint 0.6.8 → 0.7.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/README.md +90 -99
- package/backend/src/lib/api-keys.ts +14 -6
- package/backend/src/lib/lifecycle-service.ts +397 -73
- package/dist/backend/index.js +13039 -10865
- package/dist/backend-account-runtime-cleanup.js +152 -42
- package/dist/backend-api-key-facade.js +207 -61
- package/dist/backend-pipeline-cancellation.js +144 -42
- package/dist/client.d.ts +12 -2
- package/dist/client.js +83 -30
- package/dist/frontend/{CategoryDialog-CbXBMBZB.js → CategoryDialog-t_WIvFAG.js} +5 -5
- package/dist/frontend/{ConfirmDialog-DgMTQBq4.js → ConfirmDialog-_LlrA--f.js} +3 -3
- package/dist/frontend/{CreateSnapshotDialog-FUq-ZfBH.js → CreateSnapshotDialog-DW3PwAt5.js} +4 -4
- package/dist/frontend/{DatePicker-D6E-EEtZ.js → DatePicker-CAw0wZZK.js} +9 -9
- package/dist/frontend/{DeleteDialog-8k6JYWCx.js → DeleteDialog-L1A3FhMM.js} +1 -1
- package/dist/frontend/{FolderNode-DAAn_T8_.js → FolderNode-DzTqitR8.js} +2 -2
- package/dist/frontend/{HiAiEditor-DSVG3GNd.js → HiAiEditor-B7MK19_j.js} +7 -7
- package/dist/frontend/{SettingsDialog-BDNgf5rv.js → SettingsDialog-CS5L4nnU.js} +13 -13
- package/dist/frontend/ShareDialog-CwFa8mlk.js +281 -0
- package/dist/frontend/{Sidebar-DX08utdV.js → Sidebar-CDaFFmaQ.js} +18 -17
- package/dist/frontend/{TagCreateDialog-Bb0-Vtzh.js → TagCreateDialog-C7Avn2_h.js} +4 -4
- package/dist/frontend/api/attachments.js +1 -1
- package/dist/frontend/api/settings.js +1 -1
- package/dist/frontend/app-shell.js +3 -3
- package/dist/frontend/{attachments-B8AuDjno.js → attachments-B2aH3hXF.js} +10 -9
- package/dist/frontend/{auth-client-Cygmp1I2.js → auth-client-CnDZwPfp.js} +11 -11
- package/dist/frontend/components/category-dialog.js +1 -1
- package/dist/frontend/components/create-snapshot-dialog.js +1 -1
- package/dist/frontend/components/delete-dialog.js +1 -1
- package/dist/frontend/components/editor/compact-editor.js +1 -1
- package/dist/frontend/components/editor/document-editor.js +1 -1
- package/dist/frontend/components/editor/document-title.js +1 -1
- package/dist/frontend/components/editor/markdown-toggle.js +1 -1
- package/dist/frontend/components/folder-node.js +1 -1
- package/dist/frontend/components/save-as-dialog.js +4 -4
- package/dist/frontend/components/settings.js +1 -1
- package/dist/frontend/components/share-dialog.d.ts +2 -1
- package/dist/frontend/components/share-dialog.js +1 -1
- package/dist/frontend/components/sidebar.js +1 -1
- package/dist/frontend/components/tag-create-dialog.js +1 -1
- package/dist/frontend/components/version-history.js +3 -3
- package/dist/frontend/{confirm-dialog-CzmoZw3a.js → confirm-dialog-BW2h2NE8.js} +5 -5
- package/dist/frontend/dashboard.js +1008 -998
- package/dist/frontend/{db-ByrlYfaI.js → db-Bo1t_76T.js} +1 -1
- package/dist/frontend/{dialog-Ce79C9fd.js → dialog-C2iKObEu.js} +7 -7
- package/dist/frontend/{dropdown-menu-CJ_7V3N0.js → dropdown-menu-BXIl2E26.js} +12 -12
- package/dist/frontend/{floating-layer-anchor-S5RSefGN.js → floating-layer-anchor-BGIndLY-.js} +63 -63
- package/dist/frontend/i18n.js +1 -1
- package/dist/frontend/{identity-DyWeNzu_.js → identity-Ynrglr5i.js} +1 -1
- package/dist/frontend/{input-BWlOML_m.js → input-eHFb2qiL.js} +2 -2
- package/dist/frontend/{label-C2HfJNQ2.js → label-_I1iiaNU.js} +2 -2
- package/dist/frontend/messages-DTRe4U2V.js +1093 -0
- package/dist/frontend/offline/identity.js +1 -1
- package/dist/frontend/search.js +3 -3
- package/dist/frontend/{select-value-JCAp2PmU.js → select-value-CtMyrvDJ.js} +18 -18
- package/dist/frontend/{settings-DSrUl4hW.js → settings-BR48ihB4.js} +6 -23
- package/dist/frontend/{textarea-DOAE-h42.js → textarea-2eslfANY.js} +2 -2
- package/dist/frontend/{utils-BPDRytUU.js → utils-CDBqTgoV.js} +1 -1
- package/dist/frontend-ssr/api/attachments.js +1 -1
- package/dist/frontend-ssr/api/settings.js +1 -1
- package/dist/frontend-ssr/app-shell.js +3 -3
- package/dist/frontend-ssr/assets/{CategoryDialog-QHEv_9N4.js → CategoryDialog-B3MrZUho.js} +5 -5
- package/dist/frontend-ssr/assets/{ConfirmDialog-BhAelejz.js → ConfirmDialog-D1BDXXll.js} +3 -3
- package/dist/frontend-ssr/assets/{CreateSnapshotDialog-CI8vXXcN.js → CreateSnapshotDialog-NM0n1Whr.js} +4 -4
- package/dist/frontend-ssr/assets/{DatePicker-D84cJ-Eq.js → DatePicker-Dcok9XVm.js} +4 -4
- package/dist/frontend-ssr/assets/{DeleteDialog-DG9Lm38J.js → DeleteDialog-oHT_p_vG.js} +1 -1
- package/dist/frontend-ssr/assets/{FolderNode-kmilhDjO.js → FolderNode-B84NL8Rg.js} +2 -2
- package/dist/frontend-ssr/assets/{HiAiEditor-CUOJmKzT.js → HiAiEditor-tU0jmp6L.js} +6 -6
- package/dist/frontend-ssr/assets/{SettingsDialog-BjJIiVLc.js → SettingsDialog-Dkp79L1B.js} +13 -13
- package/dist/frontend-ssr/assets/{ShareDialog-C9C-XTm4.js → ShareDialog-B1-30sRR.js} +20 -7
- package/dist/frontend-ssr/assets/{Sidebar-C2sUfGNk.js → Sidebar-CGp45ngo.js} +14 -13
- package/dist/frontend-ssr/assets/{TagCreateDialog-DUPMitlL.js → TagCreateDialog-CGGtYSso.js} +4 -4
- package/dist/frontend-ssr/assets/{attachments-Cj3fQyUp.js → attachments-BdkOp8ha.js} +5 -4
- package/dist/frontend-ssr/assets/{auth-client-x26kqY8Z.js → auth-client-CojKIzqy.js} +13 -13
- package/dist/frontend-ssr/assets/{confirm-dialog-XuyaL8sr.js → confirm-dialog-B-oV5wYk.js} +5 -5
- package/dist/frontend-ssr/assets/{db-CnxeO66n.js → db-BhwJPEkn.js} +1 -1
- package/dist/frontend-ssr/assets/{dialog-BOV1u6zP.js → dialog-1SB5neow.js} +8 -8
- package/dist/frontend-ssr/assets/{dropdown-menu-Dn_vQq9b.js → dropdown-menu-DqoOkdqB.js} +12 -12
- package/dist/frontend-ssr/assets/{floating-layer-anchor-BIAu-252.js → floating-layer-anchor-BoiiIEiK.js} +71 -71
- package/dist/frontend-ssr/assets/{identity-B3uHoDtf.js → identity-DcMMBKWg.js} +1 -1
- package/dist/frontend-ssr/assets/{input-DezGsU3y.js → input-Df-QP3Km.js} +2 -2
- package/dist/frontend-ssr/assets/{label-Bj8-TfFN.js → label-CbUOUz6L.js} +2 -2
- package/dist/frontend-ssr/assets/{messages-BAQzILs4.js → messages-B1TjVIH8.js} +2 -2
- package/dist/frontend-ssr/assets/{select-value-DBhbkNcc.js → select-value-CXEDWrZg.js} +18 -18
- package/dist/frontend-ssr/assets/{settings-Bk5Hk9Vq.js → settings-Mp8EuFSn.js} +3 -21
- package/dist/frontend-ssr/assets/{textarea-Dev-qSEc.js → textarea-CHh8NuSS.js} +2 -2
- package/dist/frontend-ssr/assets/{utils-DtlPPXpJ.js → utils-DtX0ta7y.js} +1 -1
- package/dist/frontend-ssr/components/category-dialog.js +1 -1
- package/dist/frontend-ssr/components/create-snapshot-dialog.js +1 -1
- package/dist/frontend-ssr/components/delete-dialog.js +1 -1
- package/dist/frontend-ssr/components/editor/compact-editor.js +1 -1
- package/dist/frontend-ssr/components/editor/document-editor.js +1 -1
- package/dist/frontend-ssr/components/editor/document-title.js +1 -1
- package/dist/frontend-ssr/components/editor/markdown-toggle.js +1 -1
- package/dist/frontend-ssr/components/folder-node.js +1 -1
- package/dist/frontend-ssr/components/save-as-dialog.js +4 -4
- package/dist/frontend-ssr/components/settings.js +1 -1
- package/dist/frontend-ssr/components/share-dialog.js +1 -1
- package/dist/frontend-ssr/components/sidebar.js +1 -1
- package/dist/frontend-ssr/components/tag-create-dialog.js +1 -1
- package/dist/frontend-ssr/components/version-history.js +3 -3
- package/dist/frontend-ssr/dashboard.js +32 -30
- package/dist/frontend-ssr/i18n.js +1 -1
- package/dist/frontend-ssr/offline/identity.js +1 -1
- package/dist/frontend-ssr/search.js +3 -3
- package/dist/index.d.ts +5 -5
- package/dist/index.js +2 -2
- package/dist/lifecycle-persistent.d.ts +1 -1
- package/dist/lifecycle-runtime.js +52659 -338
- package/dist/mcp-cli.js +1056 -0
- package/dist/mcp-server.d.ts +39 -0
- package/dist/mcp-server.js +1053 -0
- package/dist/storage-quota.d.ts +5 -1
- package/dist/types.d.ts +7 -0
- package/dist/workspace.d.ts +11 -2
- package/dist/workspace.js +82 -10
- package/package.json +7 -13
- package/packages/cli/src/index.ts +4 -3
- package/packages/db/src/client.ts +56 -5
- package/packages/db/src/schema.ts +192 -1
- package/packages/db/src/with-tenant.ts +20 -2
- package/server.json +42 -5
- package/dist/frontend/ShareDialog-2Mty7hm4.js +0 -258
- package/dist/frontend/messages-DIovIZrK.js +0 -1093
- package/packages/mcp-server/src/capabilities.ts +0 -189
- package/packages/mcp-server/src/client.ts +0 -225
- package/packages/mcp-server/src/index.ts +0 -8
- package/packages/mcp-server/src/server.ts +0 -87
- package/packages/mcp-server/src/tools/create-document.ts +0 -34
- package/packages/mcp-server/src/tools/create-folder.ts +0 -31
- package/packages/mcp-server/src/tools/create-snapshot.ts +0 -27
- package/packages/mcp-server/src/tools/export-document.ts +0 -22
- package/packages/mcp-server/src/tools/get-document.ts +0 -22
- package/packages/mcp-server/src/tools/list-documents.ts +0 -34
- package/packages/mcp-server/src/tools/list-folders.ts +0 -26
- package/packages/mcp-server/src/tools/search.ts +0 -39
- package/packages/mcp-server/src/tools/update-document.ts +0 -38
- package/packages/mcp-server/src/tools/version-history.ts +0 -27
- package/packages/mcp-server/src/types.ts +0 -126
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
accounts,
|
|
3
|
+
apiKeys,
|
|
4
|
+
attachmentStorageCleanupOutbox,
|
|
3
5
|
attachments,
|
|
4
6
|
auditLog,
|
|
5
7
|
categories,
|
|
@@ -7,6 +9,7 @@ import {
|
|
|
7
9
|
documents,
|
|
8
10
|
folders,
|
|
9
11
|
lifecycleOperations,
|
|
12
|
+
pendingAttachmentUploads,
|
|
10
13
|
sessions,
|
|
11
14
|
shareLinks,
|
|
12
15
|
tags,
|
|
@@ -23,9 +26,21 @@ import type {
|
|
|
23
26
|
UserDataExportRecord,
|
|
24
27
|
UserDataLifecycle,
|
|
25
28
|
} from "@hiai-docs/sdk";
|
|
26
|
-
import { and, eq, inArray, lt, or, sql } from "drizzle-orm";
|
|
29
|
+
import { and, asc, eq, inArray, isNull, lt, or, sql } from "drizzle-orm";
|
|
30
|
+
import {
|
|
31
|
+
drainAttachmentStorageCleanupOutbox,
|
|
32
|
+
stageAttachmentStorageCleanup,
|
|
33
|
+
} from "./attachment-storage-cleanup";
|
|
34
|
+
import {
|
|
35
|
+
type PendingAttachmentUploadRow,
|
|
36
|
+
stagePendingAttachmentCleanup,
|
|
37
|
+
} from "./attachment-upload-cleanup";
|
|
38
|
+
import { acquireDocumentPipelineLocks } from "./document-pipeline-serialization";
|
|
39
|
+
import type { RuntimeAttachmentQuotaAdmission } from "./runtime-options";
|
|
40
|
+
import { getDocsMintRuntimeOptions } from "./runtime-options";
|
|
27
41
|
|
|
28
42
|
const LEASE_MS = 60_000;
|
|
43
|
+
export const LIFECYCLE_CLEANUP_PAGE_SIZE = 100;
|
|
29
44
|
|
|
30
45
|
type PersistentOperation = typeof lifecycleOperations.$inferSelect;
|
|
31
46
|
|
|
@@ -49,6 +64,16 @@ export class LifecycleLeaseLostError extends Error {
|
|
|
49
64
|
}
|
|
50
65
|
}
|
|
51
66
|
|
|
67
|
+
/** An issued direct-upload URL remains usable until its fixed expiry. */
|
|
68
|
+
export class LifecyclePendingAttachmentUploadsError extends Error {
|
|
69
|
+
readonly code = "pending_attachment_uploads";
|
|
70
|
+
|
|
71
|
+
constructor() {
|
|
72
|
+
super("Pending attachment upload URLs have not expired");
|
|
73
|
+
this.name = "LifecyclePendingAttachmentUploadsError";
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
52
77
|
export type LifecycleRuntimeAdapters = Readonly<{
|
|
53
78
|
/** Checks the host-owned fence immediately before the first mutation. */
|
|
54
79
|
verifyPurgeFence: (
|
|
@@ -75,6 +100,8 @@ export type LifecycleRuntimeAdapters = Readonly<{
|
|
|
75
100
|
documentIds: readonly string[],
|
|
76
101
|
signal?: AbortSignal,
|
|
77
102
|
) => Promise<number>;
|
|
103
|
+
/** Generic OSS quota saga used while settling workspace attachment cleanup. */
|
|
104
|
+
attachmentStorageQuotaAdmission?: RuntimeAttachmentQuotaAdmission;
|
|
78
105
|
}>;
|
|
79
106
|
|
|
80
107
|
/**
|
|
@@ -116,6 +143,8 @@ function safeErrorCode(error: unknown): string {
|
|
|
116
143
|
return "aborted";
|
|
117
144
|
if (error instanceof LifecycleFenceRejectedError) return error.code;
|
|
118
145
|
if (error instanceof LifecycleLeaseLostError) return error.code;
|
|
146
|
+
if (error instanceof LifecyclePendingAttachmentUploadsError)
|
|
147
|
+
return error.code;
|
|
119
148
|
return "persistence_failed";
|
|
120
149
|
}
|
|
121
150
|
|
|
@@ -509,7 +538,17 @@ export function createPersistentLifecycleService(
|
|
|
509
538
|
const deletedByDomain = operationCounts(operation);
|
|
510
539
|
try {
|
|
511
540
|
throwIfAborted(ctx.signal);
|
|
541
|
+
await runtime.verifyPurgeFence(ctx, gate.fenceToken);
|
|
512
542
|
await withActor(ctx.actorUserId, async (tx) => {
|
|
543
|
+
await tx.execute(
|
|
544
|
+
sql`SELECT public.acquire_account_purge_fence_lock(${ctx.actorUserId}::uuid)`,
|
|
545
|
+
);
|
|
546
|
+
const [actor] = await tx
|
|
547
|
+
.select({ id: users.id })
|
|
548
|
+
.from(users)
|
|
549
|
+
.where(eq(users.id, ctx.actorUserId))
|
|
550
|
+
.limit(1);
|
|
551
|
+
if (!actor) throw new LifecycleLeaseLostError();
|
|
513
552
|
const updated = await tx
|
|
514
553
|
.update(lifecycleOperations)
|
|
515
554
|
.set({
|
|
@@ -525,10 +564,21 @@ export function createPersistentLifecycleService(
|
|
|
525
564
|
.returning({ id: lifecycleOperations.id });
|
|
526
565
|
requireLeaseWrite(updated);
|
|
527
566
|
});
|
|
528
|
-
|
|
567
|
+
const withCleanupActor = <T>(
|
|
568
|
+
operationAction: (tx: TenantTransaction) => Promise<T>,
|
|
569
|
+
) =>
|
|
570
|
+
withActor(ctx.actorUserId, async (tx) => {
|
|
571
|
+
await tx.execute(
|
|
572
|
+
sql`SELECT set_config('app.lifecycle_operation_id', ${operation.id}, true)`,
|
|
573
|
+
);
|
|
574
|
+
await tx.execute(
|
|
575
|
+
sql`SELECT set_config('app.lifecycle_lease_owner', ${leaseOwner}, true)`,
|
|
576
|
+
);
|
|
577
|
+
return operationAction(tx);
|
|
578
|
+
});
|
|
529
579
|
const dbDelete = <T extends { id: string }>(
|
|
530
580
|
action: (tx: TenantTransaction) => Promise<T[]>,
|
|
531
|
-
) =>
|
|
581
|
+
) => withCleanupActor(action).then((rows) => rows.length);
|
|
532
582
|
await runStep(
|
|
533
583
|
operation,
|
|
534
584
|
ctx.actorUserId,
|
|
@@ -537,6 +587,126 @@ export function createPersistentLifecycleService(
|
|
|
537
587
|
deletedByDomain,
|
|
538
588
|
() => runtime.cancelAccountJobs(ctx.actorUserId, ctx.signal),
|
|
539
589
|
);
|
|
590
|
+
await runStep(
|
|
591
|
+
operation,
|
|
592
|
+
ctx.actorUserId,
|
|
593
|
+
leaseOwner,
|
|
594
|
+
"settle_pending_attachment_uploads",
|
|
595
|
+
deletedByDomain,
|
|
596
|
+
async () => {
|
|
597
|
+
let staged = 0;
|
|
598
|
+
for (;;) {
|
|
599
|
+
const page = await withCleanupActor(async (tx) => {
|
|
600
|
+
const pending = await tx
|
|
601
|
+
.select({
|
|
602
|
+
id: pendingAttachmentUploads.id,
|
|
603
|
+
documentId: pendingAttachmentUploads.documentId,
|
|
604
|
+
ownerUserId: sql<string>`public.lifecycle_child_document_owner(
|
|
605
|
+
${pendingAttachmentUploads.documentId},
|
|
606
|
+
${ctx.actorUserId}::uuid
|
|
607
|
+
)`,
|
|
608
|
+
actorUserId: pendingAttachmentUploads.actorUserId,
|
|
609
|
+
workspaceId: pendingAttachmentUploads.workspaceId,
|
|
610
|
+
storageKey: pendingAttachmentUploads.storageKey,
|
|
611
|
+
tokenHash: pendingAttachmentUploads.tokenHash,
|
|
612
|
+
filename: pendingAttachmentUploads.filename,
|
|
613
|
+
mimeType: pendingAttachmentUploads.mimeType,
|
|
614
|
+
declaredSize: pendingAttachmentUploads.declaredSize,
|
|
615
|
+
quotaReservationId:
|
|
616
|
+
pendingAttachmentUploads.quotaReservationId,
|
|
617
|
+
quotaOperationKey:
|
|
618
|
+
pendingAttachmentUploads.quotaOperationKey,
|
|
619
|
+
quotaState: pendingAttachmentUploads.quotaState,
|
|
620
|
+
actualSize: pendingAttachmentUploads.actualSize,
|
|
621
|
+
urlIssuedAt: pendingAttachmentUploads.urlIssuedAt,
|
|
622
|
+
expiresAt: pendingAttachmentUploads.expiresAt,
|
|
623
|
+
leaseOwner: pendingAttachmentUploads.leaseOwner,
|
|
624
|
+
})
|
|
625
|
+
.from(pendingAttachmentUploads)
|
|
626
|
+
.where(
|
|
627
|
+
eq(pendingAttachmentUploads.actorUserId, ctx.actorUserId),
|
|
628
|
+
)
|
|
629
|
+
.orderBy(asc(pendingAttachmentUploads.id))
|
|
630
|
+
.limit(LIFECYCLE_CLEANUP_PAGE_SIZE);
|
|
631
|
+
for (const row of pending) {
|
|
632
|
+
await stagePendingAttachmentCleanup(
|
|
633
|
+
tx,
|
|
634
|
+
row as PendingAttachmentUploadRow,
|
|
635
|
+
ctx.actorUserId,
|
|
636
|
+
);
|
|
637
|
+
}
|
|
638
|
+
return pending.length;
|
|
639
|
+
});
|
|
640
|
+
staged += page;
|
|
641
|
+
if (page === 0) break;
|
|
642
|
+
}
|
|
643
|
+
const leftover = await withCleanupActor((tx) =>
|
|
644
|
+
tx
|
|
645
|
+
.select({ id: attachmentStorageCleanupOutbox.id })
|
|
646
|
+
.from(attachmentStorageCleanupOutbox)
|
|
647
|
+
.where(
|
|
648
|
+
or(
|
|
649
|
+
eq(
|
|
650
|
+
attachmentStorageCleanupOutbox.actorUserId,
|
|
651
|
+
ctx.actorUserId,
|
|
652
|
+
),
|
|
653
|
+
eq(
|
|
654
|
+
attachmentStorageCleanupOutbox.ownerUserId,
|
|
655
|
+
ctx.actorUserId,
|
|
656
|
+
),
|
|
657
|
+
eq(
|
|
658
|
+
attachmentStorageCleanupOutbox.requestedByUserId,
|
|
659
|
+
ctx.actorUserId,
|
|
660
|
+
),
|
|
661
|
+
),
|
|
662
|
+
)
|
|
663
|
+
.limit(1),
|
|
664
|
+
);
|
|
665
|
+
if (staged > 0 || leftover.length > 0) {
|
|
666
|
+
for (let page = 0; page < 10_000; page += 1) {
|
|
667
|
+
const result = await drainAttachmentStorageCleanupOutbox({
|
|
668
|
+
deleteObjects: runtime.deleteObjects,
|
|
669
|
+
actorUserId: ctx.actorUserId,
|
|
670
|
+
quotaAdmission:
|
|
671
|
+
runtime.attachmentStorageQuotaAdmission ??
|
|
672
|
+
getDocsMintRuntimeOptions()
|
|
673
|
+
?.attachmentStorageQuotaAdmission,
|
|
674
|
+
signal: ctx.signal,
|
|
675
|
+
pageSize: LIFECYCLE_CLEANUP_PAGE_SIZE,
|
|
676
|
+
maxPages: 1,
|
|
677
|
+
});
|
|
678
|
+
if (result.claimed === 0) break;
|
|
679
|
+
if (result.failed > 0 || result.deferred > 0)
|
|
680
|
+
throw new LifecyclePendingAttachmentUploadsError();
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
const retained = await withCleanupActor((tx) =>
|
|
684
|
+
tx
|
|
685
|
+
.select({ id: attachmentStorageCleanupOutbox.id })
|
|
686
|
+
.from(attachmentStorageCleanupOutbox)
|
|
687
|
+
.where(
|
|
688
|
+
or(
|
|
689
|
+
eq(
|
|
690
|
+
attachmentStorageCleanupOutbox.actorUserId,
|
|
691
|
+
ctx.actorUserId,
|
|
692
|
+
),
|
|
693
|
+
eq(
|
|
694
|
+
attachmentStorageCleanupOutbox.ownerUserId,
|
|
695
|
+
ctx.actorUserId,
|
|
696
|
+
),
|
|
697
|
+
eq(
|
|
698
|
+
attachmentStorageCleanupOutbox.requestedByUserId,
|
|
699
|
+
ctx.actorUserId,
|
|
700
|
+
),
|
|
701
|
+
),
|
|
702
|
+
)
|
|
703
|
+
.limit(1),
|
|
704
|
+
);
|
|
705
|
+
if (retained.length > 0)
|
|
706
|
+
throw new LifecyclePendingAttachmentUploadsError();
|
|
707
|
+
return staged;
|
|
708
|
+
},
|
|
709
|
+
);
|
|
540
710
|
await runStep(
|
|
541
711
|
operation,
|
|
542
712
|
ctx.actorUserId,
|
|
@@ -563,7 +733,12 @@ export function createPersistentLifecycleService(
|
|
|
563
733
|
tx
|
|
564
734
|
.select({ id: documents.id })
|
|
565
735
|
.from(documents)
|
|
566
|
-
.where(
|
|
736
|
+
.where(
|
|
737
|
+
and(
|
|
738
|
+
eq(documents.ownerId, ctx.actorUserId),
|
|
739
|
+
isNull(documents.workspaceId),
|
|
740
|
+
),
|
|
741
|
+
),
|
|
567
742
|
);
|
|
568
743
|
const documentIds = owned.map((row) => row.id);
|
|
569
744
|
await runStep(
|
|
@@ -573,14 +748,19 @@ export function createPersistentLifecycleService(
|
|
|
573
748
|
"remove_document_versions",
|
|
574
749
|
deletedByDomain,
|
|
575
750
|
() =>
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
751
|
+
dbDelete((tx) =>
|
|
752
|
+
tx
|
|
753
|
+
.delete(versions)
|
|
754
|
+
.where(
|
|
755
|
+
documentIds.length
|
|
756
|
+
? or(
|
|
757
|
+
eq(versions.createdBy, ctx.actorUserId),
|
|
758
|
+
inArray(versions.documentId, documentIds),
|
|
759
|
+
)
|
|
760
|
+
: eq(versions.createdBy, ctx.actorUserId),
|
|
582
761
|
)
|
|
583
|
-
|
|
762
|
+
.returning({ id: versions.id }),
|
|
763
|
+
),
|
|
584
764
|
);
|
|
585
765
|
await runStep(
|
|
586
766
|
operation,
|
|
@@ -606,29 +786,183 @@ export function createPersistentLifecycleService(
|
|
|
606
786
|
deletedByDomain,
|
|
607
787
|
() => runtime.removeGraphState(documentIds, ctx.signal),
|
|
608
788
|
);
|
|
609
|
-
const objectRows = documentIds.length
|
|
610
|
-
? await withActor(ctx.actorUserId, (tx) =>
|
|
611
|
-
tx
|
|
612
|
-
.select({
|
|
613
|
-
id: attachments.id,
|
|
614
|
-
storageKey: attachments.storageKey,
|
|
615
|
-
})
|
|
616
|
-
.from(attachments)
|
|
617
|
-
.where(inArray(attachments.documentId, documentIds)),
|
|
618
|
-
)
|
|
619
|
-
: [];
|
|
620
789
|
await runStep(
|
|
621
790
|
operation,
|
|
622
791
|
ctx.actorUserId,
|
|
623
792
|
leaseOwner,
|
|
624
793
|
"delete_attachment_objects",
|
|
625
794
|
deletedByDomain,
|
|
795
|
+
async () => {
|
|
796
|
+
let removed = 0;
|
|
797
|
+
for (;;) {
|
|
798
|
+
const page = await withCleanupActor(async (tx) => {
|
|
799
|
+
const objectRows = await tx
|
|
800
|
+
.select({
|
|
801
|
+
id: attachments.id,
|
|
802
|
+
documentId: attachments.documentId,
|
|
803
|
+
storageKey: attachments.storageKey,
|
|
804
|
+
size: attachments.size,
|
|
805
|
+
uploadedBy: attachments.uploadedBy,
|
|
806
|
+
workspaceId: attachments.workspaceId,
|
|
807
|
+
ownerUserId: sql<string>`public.lifecycle_child_document_owner(
|
|
808
|
+
${attachments.documentId},
|
|
809
|
+
${ctx.actorUserId}::uuid
|
|
810
|
+
)`,
|
|
811
|
+
})
|
|
812
|
+
.from(attachments)
|
|
813
|
+
.where(
|
|
814
|
+
documentIds.length
|
|
815
|
+
? or(
|
|
816
|
+
eq(attachments.uploadedBy, ctx.actorUserId),
|
|
817
|
+
inArray(attachments.documentId, documentIds),
|
|
818
|
+
)
|
|
819
|
+
: eq(attachments.uploadedBy, ctx.actorUserId),
|
|
820
|
+
)
|
|
821
|
+
.orderBy(asc(attachments.id))
|
|
822
|
+
.limit(LIFECYCLE_CLEANUP_PAGE_SIZE);
|
|
823
|
+
if (objectRows.length === 0) return 0;
|
|
824
|
+
for (const row of objectRows) {
|
|
825
|
+
await stageAttachmentStorageCleanup(tx, {
|
|
826
|
+
sourceKind: "attachment",
|
|
827
|
+
sourceId: row.id,
|
|
828
|
+
storageKey: row.storageKey,
|
|
829
|
+
documentId: row.documentId,
|
|
830
|
+
actorUserId: row.uploadedBy,
|
|
831
|
+
ownerUserId: row.ownerUserId,
|
|
832
|
+
requestedByUserId: ctx.actorUserId,
|
|
833
|
+
workspaceId: row.workspaceId,
|
|
834
|
+
size: row.size,
|
|
835
|
+
quotaOperationKey: `attachment:${row.documentId}:${row.storageKey}`,
|
|
836
|
+
quotaReleaseKind: row.workspaceId ? "committed" : "none",
|
|
837
|
+
});
|
|
838
|
+
}
|
|
839
|
+
const deleted = await tx
|
|
840
|
+
.delete(attachments)
|
|
841
|
+
.where(
|
|
842
|
+
inArray(
|
|
843
|
+
attachments.id,
|
|
844
|
+
objectRows.map((row) => row.id),
|
|
845
|
+
),
|
|
846
|
+
)
|
|
847
|
+
.returning({ id: attachments.id });
|
|
848
|
+
return deleted.length;
|
|
849
|
+
});
|
|
850
|
+
removed += page;
|
|
851
|
+
if (page === 0) break;
|
|
852
|
+
}
|
|
853
|
+
return removed;
|
|
854
|
+
},
|
|
855
|
+
);
|
|
856
|
+
await runStep(
|
|
857
|
+
operation,
|
|
858
|
+
ctx.actorUserId,
|
|
859
|
+
leaseOwner,
|
|
860
|
+
"release_attachment_quota",
|
|
861
|
+
deletedByDomain,
|
|
862
|
+
async () => {
|
|
863
|
+
let deleted = 0;
|
|
864
|
+
for (let page = 0; page < 10_000; page += 1) {
|
|
865
|
+
const result = await drainAttachmentStorageCleanupOutbox({
|
|
866
|
+
deleteObjects: runtime.deleteObjects,
|
|
867
|
+
actorUserId: ctx.actorUserId,
|
|
868
|
+
quotaAdmission:
|
|
869
|
+
runtime.attachmentStorageQuotaAdmission ??
|
|
870
|
+
getDocsMintRuntimeOptions()?.attachmentStorageQuotaAdmission,
|
|
871
|
+
signal: ctx.signal,
|
|
872
|
+
pageSize: LIFECYCLE_CLEANUP_PAGE_SIZE,
|
|
873
|
+
maxPages: 1,
|
|
874
|
+
});
|
|
875
|
+
deleted += result.deleted;
|
|
876
|
+
if (result.claimed === 0) break;
|
|
877
|
+
if (result.failed > 0 || result.deferred > 0)
|
|
878
|
+
throw new LifecyclePendingAttachmentUploadsError();
|
|
879
|
+
}
|
|
880
|
+
const retained = await withCleanupActor((tx) =>
|
|
881
|
+
tx
|
|
882
|
+
.select({ id: attachmentStorageCleanupOutbox.id })
|
|
883
|
+
.from(attachmentStorageCleanupOutbox)
|
|
884
|
+
.where(
|
|
885
|
+
or(
|
|
886
|
+
eq(
|
|
887
|
+
attachmentStorageCleanupOutbox.actorUserId,
|
|
888
|
+
ctx.actorUserId,
|
|
889
|
+
),
|
|
890
|
+
eq(
|
|
891
|
+
attachmentStorageCleanupOutbox.ownerUserId,
|
|
892
|
+
ctx.actorUserId,
|
|
893
|
+
),
|
|
894
|
+
eq(
|
|
895
|
+
attachmentStorageCleanupOutbox.requestedByUserId,
|
|
896
|
+
ctx.actorUserId,
|
|
897
|
+
),
|
|
898
|
+
),
|
|
899
|
+
)
|
|
900
|
+
.limit(1),
|
|
901
|
+
);
|
|
902
|
+
if (retained.length > 0)
|
|
903
|
+
throw new LifecyclePendingAttachmentUploadsError();
|
|
904
|
+
return deleted;
|
|
905
|
+
},
|
|
906
|
+
);
|
|
907
|
+
await runStep(
|
|
908
|
+
operation,
|
|
909
|
+
ctx.actorUserId,
|
|
910
|
+
leaseOwner,
|
|
911
|
+
"remove_attachment_rows",
|
|
912
|
+
deletedByDomain,
|
|
913
|
+
() => Promise.resolve(0),
|
|
914
|
+
);
|
|
915
|
+
await runStep(
|
|
916
|
+
operation,
|
|
917
|
+
ctx.actorUserId,
|
|
918
|
+
leaseOwner,
|
|
919
|
+
"clear_redis_state",
|
|
920
|
+
deletedByDomain,
|
|
921
|
+
() => runtime.clearAccountRedisState(ctx.actorUserId, ctx.signal),
|
|
922
|
+
);
|
|
923
|
+
for (const step of orderedSteps)
|
|
924
|
+
if (step.purge)
|
|
925
|
+
await runStep(
|
|
926
|
+
operation,
|
|
927
|
+
ctx.actorUserId,
|
|
928
|
+
leaseOwner,
|
|
929
|
+
`host:${step.id}`,
|
|
930
|
+
deletedByDomain,
|
|
931
|
+
async () => (await step.purge?.(ctx))?.deletedCount ?? 0,
|
|
932
|
+
);
|
|
933
|
+
await runStep(
|
|
934
|
+
operation,
|
|
935
|
+
ctx.actorUserId,
|
|
936
|
+
leaseOwner,
|
|
937
|
+
"remove_subject_documents",
|
|
938
|
+
deletedByDomain,
|
|
626
939
|
() =>
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
940
|
+
dbDelete(async (tx) => {
|
|
941
|
+
const currentDocuments = await tx
|
|
942
|
+
.select({ id: documents.id })
|
|
943
|
+
.from(documents)
|
|
944
|
+
.where(
|
|
945
|
+
and(
|
|
946
|
+
eq(documents.ownerId, ctx.actorUserId),
|
|
947
|
+
isNull(documents.workspaceId),
|
|
948
|
+
),
|
|
949
|
+
);
|
|
950
|
+
const currentDocumentIds = currentDocuments.map(({ id }) => id);
|
|
951
|
+
await acquireDocumentPipelineLocks(tx, currentDocumentIds);
|
|
952
|
+
if (currentDocumentIds.length === 0) return [];
|
|
953
|
+
return tx
|
|
954
|
+
.delete(documents)
|
|
955
|
+
.where(
|
|
956
|
+
and(
|
|
957
|
+
eq(documents.ownerId, ctx.actorUserId),
|
|
958
|
+
isNull(documents.workspaceId),
|
|
959
|
+
),
|
|
960
|
+
)
|
|
961
|
+
.returning({ id: documents.id });
|
|
962
|
+
}),
|
|
631
963
|
);
|
|
964
|
+
// Parent metadata uses ON DELETE SET NULL. Delete documents first so
|
|
965
|
+
// those referential actions do not become fenced document UPDATEs.
|
|
632
966
|
await runStep(
|
|
633
967
|
operation,
|
|
634
968
|
ctx.actorUserId,
|
|
@@ -639,7 +973,12 @@ export function createPersistentLifecycleService(
|
|
|
639
973
|
dbDelete((tx) =>
|
|
640
974
|
tx
|
|
641
975
|
.delete(folders)
|
|
642
|
-
.where(
|
|
976
|
+
.where(
|
|
977
|
+
and(
|
|
978
|
+
eq(folders.ownerId, ctx.actorUserId),
|
|
979
|
+
isNull(folders.workspaceId),
|
|
980
|
+
),
|
|
981
|
+
)
|
|
643
982
|
.returning({ id: folders.id }),
|
|
644
983
|
),
|
|
645
984
|
);
|
|
@@ -653,7 +992,12 @@ export function createPersistentLifecycleService(
|
|
|
653
992
|
dbDelete((tx) =>
|
|
654
993
|
tx
|
|
655
994
|
.delete(tags)
|
|
656
|
-
.where(
|
|
995
|
+
.where(
|
|
996
|
+
and(
|
|
997
|
+
eq(tags.ownerId, ctx.actorUserId),
|
|
998
|
+
isNull(tags.workspaceId),
|
|
999
|
+
),
|
|
1000
|
+
)
|
|
657
1001
|
.returning({ id: tags.id }),
|
|
658
1002
|
),
|
|
659
1003
|
);
|
|
@@ -667,7 +1011,12 @@ export function createPersistentLifecycleService(
|
|
|
667
1011
|
dbDelete((tx) =>
|
|
668
1012
|
tx
|
|
669
1013
|
.delete(categories)
|
|
670
|
-
.where(
|
|
1014
|
+
.where(
|
|
1015
|
+
and(
|
|
1016
|
+
eq(categories.ownerId, ctx.actorUserId),
|
|
1017
|
+
isNull(categories.workspaceId),
|
|
1018
|
+
),
|
|
1019
|
+
)
|
|
671
1020
|
.returning({ id: categories.id }),
|
|
672
1021
|
),
|
|
673
1022
|
);
|
|
@@ -675,55 +1024,16 @@ export function createPersistentLifecycleService(
|
|
|
675
1024
|
operation,
|
|
676
1025
|
ctx.actorUserId,
|
|
677
1026
|
leaseOwner,
|
|
678
|
-
"
|
|
679
|
-
deletedByDomain,
|
|
680
|
-
() =>
|
|
681
|
-
objectRows.length
|
|
682
|
-
? dbDelete((tx) =>
|
|
683
|
-
tx
|
|
684
|
-
.delete(attachments)
|
|
685
|
-
.where(
|
|
686
|
-
inArray(
|
|
687
|
-
attachments.id,
|
|
688
|
-
objectRows.map((row) => row.id),
|
|
689
|
-
),
|
|
690
|
-
)
|
|
691
|
-
.returning({ id: attachments.id }),
|
|
692
|
-
)
|
|
693
|
-
: Promise.resolve(0),
|
|
694
|
-
);
|
|
695
|
-
await runStep(
|
|
696
|
-
operation,
|
|
697
|
-
ctx.actorUserId,
|
|
698
|
-
leaseOwner,
|
|
699
|
-
"remove_subject_documents",
|
|
1027
|
+
"remove_api_keys",
|
|
700
1028
|
deletedByDomain,
|
|
701
1029
|
() =>
|
|
702
1030
|
dbDelete((tx) =>
|
|
703
1031
|
tx
|
|
704
|
-
.delete(
|
|
705
|
-
.where(eq(
|
|
706
|
-
.returning({ id:
|
|
1032
|
+
.delete(apiKeys)
|
|
1033
|
+
.where(eq(apiKeys.ownerId, ctx.actorUserId))
|
|
1034
|
+
.returning({ id: apiKeys.id }),
|
|
707
1035
|
),
|
|
708
1036
|
);
|
|
709
|
-
await runStep(
|
|
710
|
-
operation,
|
|
711
|
-
ctx.actorUserId,
|
|
712
|
-
leaseOwner,
|
|
713
|
-
"clear_redis_state",
|
|
714
|
-
deletedByDomain,
|
|
715
|
-
() => runtime.clearAccountRedisState(ctx.actorUserId, ctx.signal),
|
|
716
|
-
);
|
|
717
|
-
for (const step of orderedSteps)
|
|
718
|
-
if (step.purge)
|
|
719
|
-
await runStep(
|
|
720
|
-
operation,
|
|
721
|
-
ctx.actorUserId,
|
|
722
|
-
leaseOwner,
|
|
723
|
-
`host:${step.id}`,
|
|
724
|
-
deletedByDomain,
|
|
725
|
-
async () => (await step.purge?.(ctx))?.deletedCount ?? 0,
|
|
726
|
-
);
|
|
727
1037
|
await runStep(
|
|
728
1038
|
operation,
|
|
729
1039
|
ctx.actorUserId,
|
|
@@ -752,6 +1062,20 @@ export function createPersistentLifecycleService(
|
|
|
752
1062
|
.returning({ id: accounts.id }),
|
|
753
1063
|
),
|
|
754
1064
|
);
|
|
1065
|
+
await runStep(
|
|
1066
|
+
operation,
|
|
1067
|
+
ctx.actorUserId,
|
|
1068
|
+
leaseOwner,
|
|
1069
|
+
"remove_subject_audit",
|
|
1070
|
+
deletedByDomain,
|
|
1071
|
+
() =>
|
|
1072
|
+
dbDelete((tx) =>
|
|
1073
|
+
tx
|
|
1074
|
+
.delete(auditLog)
|
|
1075
|
+
.where(eq(auditLog.actorId, ctx.actorUserId))
|
|
1076
|
+
.returning({ id: auditLog.id }),
|
|
1077
|
+
),
|
|
1078
|
+
);
|
|
755
1079
|
await runStep(
|
|
756
1080
|
operation,
|
|
757
1081
|
ctx.actorUserId,
|
|
@@ -780,7 +1104,7 @@ export function createPersistentLifecycleService(
|
|
|
780
1104
|
"write_deletion_audit",
|
|
781
1105
|
deletedByDomain,
|
|
782
1106
|
async () => {
|
|
783
|
-
await
|
|
1107
|
+
await withCleanupActor((tx) =>
|
|
784
1108
|
tx.insert(auditLog).values({
|
|
785
1109
|
actorId: ctx.actorUserId,
|
|
786
1110
|
action: "account_data_purged",
|