@cosmicdrift/kumiko-bundled-features 0.352.0 → 0.353.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent-tools/agent-manifest.js +8 -2
- package/dist/agent-tools/manifest-deny-qns.d.ts +3 -0
- package/dist/agent-tools/manifest-deny-qns.js +17 -0
- package/dist/agent-tools/tool-catalog.d.ts +2 -1
- package/dist/agent-tools/tool-catalog.js +4 -1
- package/dist/audit/handlers/list.query.js +13 -8
- package/dist/auth-email-password/handlers/signup-confirm.write.js +2 -3
- package/dist/auth-mfa/schema/user-mfa.d.ts +6 -0
- package/dist/auth-mfa/screens.js +2 -2
- package/dist/billing-foundation/checkout-core.d.ts +7 -0
- package/dist/billing-foundation/checkout-core.js +7 -2
- package/dist/billing-foundation/entities.d.ts +8 -0
- package/dist/billing-foundation/handlers/start-plan-checkout.write.js +7 -1
- package/dist/billing-foundation/projection.d.ts +8 -0
- package/dist/cap-counter/entity.d.ts +1 -0
- package/dist/cap-overview/access.js +2 -9
- package/dist/cap-overview/handlers/tenant-caps-list.query.js +2 -2
- package/dist/cap-overview/handlers/tenant-options.query.js +4 -5
- package/dist/compliance-profiles/schema/profile-selection.d.ts +8 -8
- package/dist/compliance-profiles-ops/handlers/tenants-missing-profile.query.js +2 -4
- package/dist/config/handlers/values.query.d.ts +3 -1
- package/dist/config/handlers/values.query.js +7 -2
- package/dist/config/table.d.ts +6 -0
- package/dist/custom-fields/entity.d.ts +4 -0
- package/dist/data-retention/schema/tenant-retention-override.d.ts +4 -0
- package/dist/delivery/public-names.js +1 -1
- package/dist/delivery/tables.d.ts +6 -0
- package/dist/document-ingest-foundation/entity.d.ts +4 -0
- package/dist/files-tenant-data/hooks.js +3 -2
- package/dist/folders/entity.d.ts +8 -0
- package/dist/folders/feature.js +2 -10
- package/dist/form-draft/db/queries/cleanup.d.ts +1 -1
- package/dist/form-draft/db/queries/cleanup.js +4 -3
- package/dist/form-draft/entity.d.ts +2 -0
- package/dist/form-draft/handlers/cleanup.job.d.ts +5 -1
- package/dist/form-draft/handlers/cleanup.job.js +11 -5
- package/dist/inbound-mail-foundation/entities.d.ts +25 -0
- package/dist/inbound-mail-foundation/projection.d.ts +20 -0
- package/dist/jobs/handlers/list.query.d.ts +2 -1
- package/dist/jobs/handlers/list.query.js +39 -14
- package/dist/jobs/i18n.js +1 -0
- package/dist/jobs/job-run-table.d.ts +6 -0
- package/dist/ledger/entity.d.ts +21 -8
- package/dist/ledger/entity.js +4 -0
- package/dist/ledger/schemas.js +8 -2
- package/dist/managed-pages/feature.js +28 -10
- package/dist/managed-pages/table.d.ts +12 -0
- package/dist/notes-history/entity.d.ts +12 -0
- package/dist/notes-history/feature.js +2 -10
- package/dist/personal-access-tokens/schema/api-token.d.ts +6 -0
- package/dist/personal-access-tokens/screens.js +14 -3
- package/dist/secrets/table.d.ts +1 -0
- package/dist/seo/feature.js +28 -6
- package/dist/sessions/schema/user-session.d.ts +4 -0
- package/dist/shared/assert-no-where-rule-in-ownership-write.d.ts +2 -0
- package/dist/shared/assert-no-where-rule-in-ownership-write.js +18 -0
- package/dist/shared/index.d.ts +2 -1
- package/dist/shared/index.js +2 -1
- package/dist/shared/rate-limited-text-response.d.ts +2 -0
- package/dist/shared/rate-limited-text-response.js +9 -0
- package/dist/tags/entity.d.ts +9 -0
- package/dist/tags/feature.js +2 -10
- package/dist/template-resolver/table.d.ts +40 -30
- package/dist/template-resolver/user-content-table.d.ts +30 -20
- package/dist/tenant/decrypt-tenant-names.d.ts +3 -0
- package/dist/tenant/decrypt-tenant-names.js +10 -0
- package/dist/tenant/handlers/me.query.js +5 -1
- package/dist/tenant/handlers/memberships.query.js +3 -2
- package/dist/tenant/handlers/tenant-directory.query.js +9 -3
- package/dist/tenant/index.d.ts +1 -0
- package/dist/tenant/index.js +1 -0
- package/dist/tenant/invitation-table.d.ts +14 -8
- package/dist/tenant/membership-table.d.ts +4 -0
- package/dist/tenant/schema/tenant.d.ts +16 -10
- package/dist/tenant/schema/tenant.js +4 -6
- package/dist/tenant/screens.js +1 -1
- package/dist/tenant-handover/move-entity-graph.js +11 -18
- package/dist/tenant-lifecycle/feature.js +1 -0
- package/dist/tenant-lifecycle/run-tenant-destroy.d.ts +3 -0
- package/dist/tenant-lifecycle/run-tenant-destroy.js +2 -0
- package/dist/tenant-lifecycle/stages.d.ts +2 -0
- package/dist/tenant-lifecycle/stages.js +7 -1
- package/dist/tier-engine/entity.d.ts +2 -0
- package/dist/tier-engine/tier-resolver.d.ts +2 -0
- package/dist/user/handlers/list.query.js +2 -2
- package/dist/user/schema/user.d.ts +20 -8
- package/dist/user-data-rights/boot-checks.js +5 -0
- package/dist/user-data-rights/handlers/confirm-deletion-by-token.write.js +2 -2
- package/dist/user-data-rights/schema/download-attempt.d.ts +14 -0
- package/dist/user-data-rights/schema/download-token.d.ts +8 -0
- package/dist/user-data-rights/schema/export-job.d.ts +14 -8
- package/dist/waitlist/entity.d.ts +20 -6
- package/dist/workflow-runner/event-trigger.js +6 -3
- package/dist/workflow-runner/handlers/resume-run.write.js +3 -0
- package/package.json +9 -9
- package/src/agent-tools/changes.json +12 -0
- package/src/audit/changes.json +6 -0
- package/src/form-draft/changes.json +7 -1
- package/src/managed-pages/changes.json +6 -0
- package/src/seo/changes.json +5 -0
- package/src/tenant/changes.json +7 -0
- package/src/tenant-handover/changes.json +6 -0
- package/src/workflow-runner/changes.json +5 -0
- package/dist/shared/has-where-rule.d.ts +0 -2
- package/dist/shared/has-where-rule.js +0 -10
|
@@ -3,6 +3,7 @@ export declare const fieldDefinitionEntity: import("@cosmicdrift/kumiko-types/fi
|
|
|
3
3
|
readonly type: "text";
|
|
4
4
|
readonly description?: string;
|
|
5
5
|
readonly maxLength?: number;
|
|
6
|
+
readonly minLength?: number;
|
|
6
7
|
readonly required?: boolean;
|
|
7
8
|
readonly searchable?: boolean;
|
|
8
9
|
readonly sortable?: boolean;
|
|
@@ -24,6 +25,7 @@ export declare const fieldDefinitionEntity: import("@cosmicdrift/kumiko-types/fi
|
|
|
24
25
|
readonly type: "text";
|
|
25
26
|
readonly description?: string;
|
|
26
27
|
readonly maxLength?: number;
|
|
28
|
+
readonly minLength?: number;
|
|
27
29
|
readonly required?: boolean;
|
|
28
30
|
readonly searchable?: boolean;
|
|
29
31
|
readonly sortable?: boolean;
|
|
@@ -45,6 +47,7 @@ export declare const fieldDefinitionEntity: import("@cosmicdrift/kumiko-types/fi
|
|
|
45
47
|
readonly type: "text";
|
|
46
48
|
readonly description?: string;
|
|
47
49
|
readonly maxLength?: number;
|
|
50
|
+
readonly minLength?: number;
|
|
48
51
|
readonly required?: boolean;
|
|
49
52
|
readonly searchable?: boolean;
|
|
50
53
|
readonly sortable?: boolean;
|
|
@@ -91,6 +94,7 @@ export declare const fieldDefinitionEntity: import("@cosmicdrift/kumiko-types/fi
|
|
|
91
94
|
readonly type: "text";
|
|
92
95
|
readonly description?: string;
|
|
93
96
|
readonly maxLength?: number;
|
|
97
|
+
readonly minLength?: number;
|
|
94
98
|
readonly required?: boolean;
|
|
95
99
|
readonly searchable?: boolean;
|
|
96
100
|
readonly sortable?: boolean;
|
|
@@ -3,6 +3,7 @@ export declare const tenantRetentionOverrideEntity: import("@cosmicdrift/kumiko-
|
|
|
3
3
|
readonly type: "text";
|
|
4
4
|
readonly description?: string;
|
|
5
5
|
readonly maxLength?: number;
|
|
6
|
+
readonly minLength?: number;
|
|
6
7
|
readonly required?: boolean;
|
|
7
8
|
readonly searchable?: boolean;
|
|
8
9
|
readonly sortable?: boolean;
|
|
@@ -40,6 +41,7 @@ export declare const tenantRetentionOverrideEntity: import("@cosmicdrift/kumiko-
|
|
|
40
41
|
readonly type: "text";
|
|
41
42
|
readonly description?: string;
|
|
42
43
|
readonly maxLength?: number;
|
|
44
|
+
readonly minLength?: number;
|
|
43
45
|
readonly required?: boolean;
|
|
44
46
|
readonly searchable?: boolean;
|
|
45
47
|
readonly sortable?: boolean;
|
|
@@ -63,6 +65,7 @@ export declare const tenantRetentionOverrideTable: import("@cosmicdrift/kumiko-f
|
|
|
63
65
|
readonly type: "text";
|
|
64
66
|
readonly description?: string;
|
|
65
67
|
readonly maxLength?: number;
|
|
68
|
+
readonly minLength?: number;
|
|
66
69
|
readonly required?: boolean;
|
|
67
70
|
readonly searchable?: boolean;
|
|
68
71
|
readonly sortable?: boolean;
|
|
@@ -100,6 +103,7 @@ export declare const tenantRetentionOverrideTable: import("@cosmicdrift/kumiko-f
|
|
|
100
103
|
readonly type: "text";
|
|
101
104
|
readonly description?: string;
|
|
102
105
|
readonly maxLength?: number;
|
|
106
|
+
readonly minLength?: number;
|
|
103
107
|
readonly required?: boolean;
|
|
104
108
|
readonly searchable?: boolean;
|
|
105
109
|
readonly sortable?: boolean;
|
|
@@ -8,7 +8,7 @@ export const DeliveryHandlers = {
|
|
|
8
8
|
resubscribeAddress: "delivery:write:resubscribe-address",
|
|
9
9
|
resubscribeUser: "delivery:write:resubscribe-user",
|
|
10
10
|
};
|
|
11
|
-
//
|
|
11
|
+
// Stable path: links signed against it keep working. The unsubscribe route is
|
|
12
12
|
// mounted at this exact path via `extraRoutes: [...createUnsubscribeRoutes(...)]`.
|
|
13
13
|
export const DELIVERY_UNSUBSCRIBE_PATH = "/api/delivery/unsubscribe";
|
|
14
14
|
// Mounted alongside the unsubscribe route by the same
|
|
@@ -6,6 +6,7 @@ export declare const notificationPreferenceEntity: import("@cosmicdrift/kumiko-t
|
|
|
6
6
|
readonly type: "text";
|
|
7
7
|
readonly description?: string;
|
|
8
8
|
readonly maxLength?: number;
|
|
9
|
+
readonly minLength?: number;
|
|
9
10
|
readonly required?: boolean;
|
|
10
11
|
readonly searchable?: boolean;
|
|
11
12
|
readonly sortable?: boolean;
|
|
@@ -27,6 +28,7 @@ export declare const notificationPreferenceEntity: import("@cosmicdrift/kumiko-t
|
|
|
27
28
|
readonly type: "text";
|
|
28
29
|
readonly description?: string;
|
|
29
30
|
readonly maxLength?: number;
|
|
31
|
+
readonly minLength?: number;
|
|
30
32
|
readonly required?: boolean;
|
|
31
33
|
readonly searchable?: boolean;
|
|
32
34
|
readonly sortable?: boolean;
|
|
@@ -48,6 +50,7 @@ export declare const notificationPreferenceEntity: import("@cosmicdrift/kumiko-t
|
|
|
48
50
|
readonly type: "text";
|
|
49
51
|
readonly description?: string;
|
|
50
52
|
readonly maxLength?: number;
|
|
53
|
+
readonly minLength?: number;
|
|
51
54
|
readonly required?: boolean;
|
|
52
55
|
readonly searchable?: boolean;
|
|
53
56
|
readonly sortable?: boolean;
|
|
@@ -75,6 +78,7 @@ export declare const notificationAddressOptOutEntity: import("@cosmicdrift/kumik
|
|
|
75
78
|
readonly type: "text";
|
|
76
79
|
readonly description?: string;
|
|
77
80
|
readonly maxLength?: number;
|
|
81
|
+
readonly minLength?: number;
|
|
78
82
|
readonly required?: boolean;
|
|
79
83
|
readonly searchable?: boolean;
|
|
80
84
|
readonly sortable?: boolean;
|
|
@@ -96,6 +100,7 @@ export declare const notificationAddressOptOutEntity: import("@cosmicdrift/kumik
|
|
|
96
100
|
readonly type: "text";
|
|
97
101
|
readonly description?: string;
|
|
98
102
|
readonly maxLength?: number;
|
|
103
|
+
readonly minLength?: number;
|
|
99
104
|
readonly required?: boolean;
|
|
100
105
|
readonly searchable?: boolean;
|
|
101
106
|
readonly sortable?: boolean;
|
|
@@ -117,6 +122,7 @@ export declare const notificationAddressOptOutEntity: import("@cosmicdrift/kumik
|
|
|
117
122
|
readonly type: "text";
|
|
118
123
|
readonly description?: string;
|
|
119
124
|
readonly maxLength?: number;
|
|
125
|
+
readonly minLength?: number;
|
|
120
126
|
readonly required?: boolean;
|
|
121
127
|
readonly searchable?: boolean;
|
|
122
128
|
readonly sortable?: boolean;
|
|
@@ -14,6 +14,7 @@ export declare const documentExtractEntity: import("@cosmicdrift/kumiko-types/fi
|
|
|
14
14
|
readonly type: "text";
|
|
15
15
|
readonly description?: string;
|
|
16
16
|
readonly maxLength?: number;
|
|
17
|
+
readonly minLength?: number;
|
|
17
18
|
readonly required?: boolean;
|
|
18
19
|
readonly searchable?: boolean;
|
|
19
20
|
readonly sortable?: boolean;
|
|
@@ -35,6 +36,7 @@ export declare const documentExtractEntity: import("@cosmicdrift/kumiko-types/fi
|
|
|
35
36
|
readonly type: "text";
|
|
36
37
|
readonly description?: string;
|
|
37
38
|
readonly maxLength?: number;
|
|
39
|
+
readonly minLength?: number;
|
|
38
40
|
readonly required?: boolean;
|
|
39
41
|
readonly searchable?: boolean;
|
|
40
42
|
readonly sortable?: boolean;
|
|
@@ -75,6 +77,7 @@ export declare const documentExtractsTable: import("@cosmicdrift/kumiko-framewor
|
|
|
75
77
|
readonly type: "text";
|
|
76
78
|
readonly description?: string;
|
|
77
79
|
readonly maxLength?: number;
|
|
80
|
+
readonly minLength?: number;
|
|
78
81
|
readonly required?: boolean;
|
|
79
82
|
readonly searchable?: boolean;
|
|
80
83
|
readonly sortable?: boolean;
|
|
@@ -96,6 +99,7 @@ export declare const documentExtractsTable: import("@cosmicdrift/kumiko-framewor
|
|
|
96
99
|
readonly type: "text";
|
|
97
100
|
readonly description?: string;
|
|
98
101
|
readonly maxLength?: number;
|
|
102
|
+
readonly minLength?: number;
|
|
99
103
|
readonly required?: boolean;
|
|
100
104
|
readonly searchable?: boolean;
|
|
101
105
|
readonly sortable?: boolean;
|
|
@@ -137,8 +137,9 @@ export const fileRefStorageDestroyHook = async (tenantId, ctx) => {
|
|
|
137
137
|
}
|
|
138
138
|
// Provider resolved — a list()/delete() failure from here IS fail-closed:
|
|
139
139
|
// the "files" stage throws, tenant-lifecycle's retry/abandon handling sees
|
|
140
|
-
// it
|
|
141
|
-
//
|
|
140
|
+
// it and retries up to the stage's attempt cap before abandoning the destroy
|
|
141
|
+
// (list+delete are idempotent, so a retry converges rather than
|
|
142
|
+
// double-deleting or erroring on a missing key).
|
|
142
143
|
for (const prefix of tenantStoragePrefixes(tenantId)) {
|
|
143
144
|
const keys = await provider.list(prefix);
|
|
144
145
|
const keptStemPrefixes = await survivorStemPrefixes(ctx.db, keys);
|
package/dist/folders/entity.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ export declare const folderEntity: EntityDefinition<{
|
|
|
4
4
|
readonly type: "text";
|
|
5
5
|
readonly description?: string;
|
|
6
6
|
readonly maxLength?: number;
|
|
7
|
+
readonly minLength?: number;
|
|
7
8
|
readonly required?: boolean;
|
|
8
9
|
readonly searchable?: boolean;
|
|
9
10
|
readonly sortable?: boolean;
|
|
@@ -25,6 +26,7 @@ export declare const folderEntity: EntityDefinition<{
|
|
|
25
26
|
readonly type: "text";
|
|
26
27
|
readonly description?: string;
|
|
27
28
|
readonly maxLength?: number;
|
|
29
|
+
readonly minLength?: number;
|
|
28
30
|
readonly required?: boolean;
|
|
29
31
|
readonly searchable?: boolean;
|
|
30
32
|
readonly sortable?: boolean;
|
|
@@ -48,6 +50,7 @@ export declare function createFolderAssignmentEntity(access?: EntityDefinition["
|
|
|
48
50
|
readonly type: "text";
|
|
49
51
|
readonly description?: string;
|
|
50
52
|
readonly maxLength?: number;
|
|
53
|
+
readonly minLength?: number;
|
|
51
54
|
readonly required?: boolean;
|
|
52
55
|
readonly searchable?: boolean;
|
|
53
56
|
readonly sortable?: boolean;
|
|
@@ -69,6 +72,7 @@ export declare function createFolderAssignmentEntity(access?: EntityDefinition["
|
|
|
69
72
|
readonly type: "text";
|
|
70
73
|
readonly description?: string;
|
|
71
74
|
readonly maxLength?: number;
|
|
75
|
+
readonly minLength?: number;
|
|
72
76
|
readonly required?: boolean;
|
|
73
77
|
readonly searchable?: boolean;
|
|
74
78
|
readonly sortable?: boolean;
|
|
@@ -90,6 +94,7 @@ export declare function createFolderAssignmentEntity(access?: EntityDefinition["
|
|
|
90
94
|
readonly type: "text";
|
|
91
95
|
readonly description?: string;
|
|
92
96
|
readonly maxLength?: number;
|
|
97
|
+
readonly minLength?: number;
|
|
93
98
|
readonly required?: boolean;
|
|
94
99
|
readonly searchable?: boolean;
|
|
95
100
|
readonly sortable?: boolean;
|
|
@@ -113,6 +118,7 @@ export declare const folderAssignmentEntity: EntityDefinition<{
|
|
|
113
118
|
readonly type: "text";
|
|
114
119
|
readonly description?: string;
|
|
115
120
|
readonly maxLength?: number;
|
|
121
|
+
readonly minLength?: number;
|
|
116
122
|
readonly required?: boolean;
|
|
117
123
|
readonly searchable?: boolean;
|
|
118
124
|
readonly sortable?: boolean;
|
|
@@ -134,6 +140,7 @@ export declare const folderAssignmentEntity: EntityDefinition<{
|
|
|
134
140
|
readonly type: "text";
|
|
135
141
|
readonly description?: string;
|
|
136
142
|
readonly maxLength?: number;
|
|
143
|
+
readonly minLength?: number;
|
|
137
144
|
readonly required?: boolean;
|
|
138
145
|
readonly searchable?: boolean;
|
|
139
146
|
readonly sortable?: boolean;
|
|
@@ -155,6 +162,7 @@ export declare const folderAssignmentEntity: EntityDefinition<{
|
|
|
155
162
|
readonly type: "text";
|
|
156
163
|
readonly description?: string;
|
|
157
164
|
readonly maxLength?: number;
|
|
165
|
+
readonly minLength?: number;
|
|
158
166
|
readonly required?: boolean;
|
|
159
167
|
readonly searchable?: boolean;
|
|
160
168
|
readonly sortable?: boolean;
|
package/dist/folders/feature.js
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
// Deferred: reparenting with cycle-check (folder:update CAN change parentId, but
|
|
16
16
|
// no UI exposes it in v1 — folders-view guards cycles defensively).
|
|
17
17
|
import { defineEntityCreateHandler, defineEntityDetailHandler, defineEntityListHandler, defineEntityUpdateHandler, defineFeature, } from "@cosmicdrift/kumiko-framework/engine";
|
|
18
|
-
import {
|
|
18
|
+
import { assertNoWhereRuleInOwnershipWrite } from "../shared/index.js";
|
|
19
19
|
import { DEFAULT_FOLDER_ACCESS, FOLDERS_FEATURE_NAME } from "./constants.js";
|
|
20
20
|
import { createFolderAssignmentEntity, folderEntity } from "./entity.js";
|
|
21
21
|
import { createClearFolderHandler } from "./handlers/clear-folder.write.js";
|
|
@@ -80,15 +80,7 @@ export function createFoldersFeature(opts = {}) {
|
|
|
80
80
|
opts.parents === undefined) {
|
|
81
81
|
return foldersFeature;
|
|
82
82
|
}
|
|
83
|
-
|
|
84
|
-
throw new Error("createFoldersFeature({ ownership }): ownership.write must not contain a " +
|
|
85
|
-
'`{ kind: "where" }` rule — where-rules are evaluated only at the SQL ' +
|
|
86
|
-
"layer (the read path, via buildOwnershipClause). Write paths that " +
|
|
87
|
-
"consult access.write (userCanCreateFieldRow/userCanWriteFieldRow) can't " +
|
|
88
|
-
"evaluate them, so such a rule can only ever deny — boot validation " +
|
|
89
|
-
"rejects it too (fw#2626). Use a `from()` rule for ownership.write, or " +
|
|
90
|
-
"leave it unset.");
|
|
91
|
-
}
|
|
83
|
+
assertNoWhereRuleInOwnershipWrite("createFoldersFeature", opts.ownership?.write);
|
|
92
84
|
if (opts.parents !== undefined && opts.parents.length === 0) {
|
|
93
85
|
throw new Error("createFoldersFeature({ parents }): parents must not be an empty array — " +
|
|
94
86
|
"an empty allowlist rejects every set-folder call and hides every " +
|
|
@@ -12,4 +12,4 @@ export type StaleDraftRow = {
|
|
|
12
12
|
};
|
|
13
13
|
export declare function selectStaleDraftsBatch(db: DbConnection, olderThanDays: number, batchSize: number): Promise<readonly StaleDraftRow[]>;
|
|
14
14
|
/** Re-check staleness after the select→release window so a freshly saved draft is not deleted. */
|
|
15
|
-
export declare function isDraftStillStale(db: DbConnection, id: string, olderThanDays: number): Promise<boolean>;
|
|
15
|
+
export declare function isDraftStillStale(db: DbConnection, tenantId: TenantId, id: string, olderThanDays: number): Promise<boolean>;
|
|
@@ -26,10 +26,11 @@ export async function selectStaleDraftsBatch(db, olderThanDays, batchSize) {
|
|
|
26
26
|
}));
|
|
27
27
|
}
|
|
28
28
|
/** Re-check staleness after the select→release window so a freshly saved draft is not deleted. */
|
|
29
|
-
export async function isDraftStillStale(db, id, olderThanDays) {
|
|
29
|
+
export async function isDraftStillStale(db, tenantId, id, olderThanDays) {
|
|
30
30
|
const rows = (await unsafeReadRetrying(db, `SELECT 1 FROM "read_form_drafts"
|
|
31
31
|
WHERE "id" = $1
|
|
32
|
-
AND
|
|
33
|
-
|
|
32
|
+
AND "tenant_id" = $2
|
|
33
|
+
AND COALESCE("modified_at", "inserted_at") < now() - ($3::int * interval '1 day')
|
|
34
|
+
LIMIT 1`, [id, tenantId, olderThanDays]));
|
|
34
35
|
return rows.length > 0;
|
|
35
36
|
}
|
|
@@ -3,6 +3,7 @@ export declare const formDraftEntity: import("@cosmicdrift/kumiko-types/fields")
|
|
|
3
3
|
readonly type: "text";
|
|
4
4
|
readonly description?: string;
|
|
5
5
|
readonly maxLength?: number;
|
|
6
|
+
readonly minLength?: number;
|
|
6
7
|
readonly required?: boolean;
|
|
7
8
|
readonly searchable?: boolean;
|
|
8
9
|
readonly sortable?: boolean;
|
|
@@ -24,6 +25,7 @@ export declare const formDraftEntity: import("@cosmicdrift/kumiko-types/fields")
|
|
|
24
25
|
readonly type: "text";
|
|
25
26
|
readonly description?: string;
|
|
26
27
|
readonly maxLength?: number;
|
|
28
|
+
readonly minLength?: number;
|
|
27
29
|
readonly required?: boolean;
|
|
28
30
|
readonly searchable?: boolean;
|
|
29
31
|
readonly sortable?: boolean;
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
import { type DbConnection } from "@cosmicdrift/kumiko-framework/db";
|
|
2
|
-
import { type ConfigKeyDefinition, type JobContext, type TenantId } from "@cosmicdrift/kumiko-framework/engine";
|
|
2
|
+
import { type AppContext, type ConfigKeyDefinition, type JobContext, type TenantId } from "@cosmicdrift/kumiko-framework/engine";
|
|
3
3
|
import { type StaleDraftRow } from "../db/queries/cleanup.js";
|
|
4
4
|
export declare const FORM_DRAFT_RETENTION_DAYS_CONFIG_KEY = "form-draft:config:retention-days";
|
|
5
5
|
export declare const FORM_DRAFT_DEFAULT_RETENTION_DAYS = 30;
|
|
6
6
|
export declare const formDraftRetentionDaysConfig: ConfigKeyDefinition<"number">;
|
|
7
7
|
export declare function groupStaleDraftIdsByTenant(batch: readonly StaleDraftRow[]): ReadonlyMap<TenantId, readonly string[]>;
|
|
8
|
+
export declare function deleteStaleDraftsBatch(batch: readonly StaleDraftRow[], db: DbConnection, retentionDays: number, log: AppContext["log"]): Promise<{
|
|
9
|
+
readonly deletedRows: readonly StaleDraftRow[];
|
|
10
|
+
readonly skippedCount: number;
|
|
11
|
+
}>;
|
|
8
12
|
export declare function cleanupDraftsJob(_payload: Record<string, unknown>, ctx: JobContext, db: DbConnection): Promise<void>;
|
|
@@ -72,16 +72,19 @@ export function groupStaleDraftIdsByTenant(batch) {
|
|
|
72
72
|
// resurrects, PII included. formDraftExecutor.delete() is the event-sourced
|
|
73
73
|
// path; it needs a tenant-scoped db + a SessionUser, so rows are grouped by
|
|
74
74
|
// tenant first (see groupStaleDraftIdsByTenant above).
|
|
75
|
-
async function deleteStaleDraftsBatch(batch, db, retentionDays, log) {
|
|
75
|
+
export async function deleteStaleDraftsBatch(batch, db, retentionDays, log) {
|
|
76
76
|
const deletedRows = [];
|
|
77
|
+
let skippedCount = 0;
|
|
77
78
|
const byId = new Map(batch.map((row) => [row.id, row]));
|
|
78
79
|
for (const [tenantId, ids] of groupStaleDraftIdsByTenant(batch)) {
|
|
79
80
|
const systemUser = createSystemUser(tenantId);
|
|
80
81
|
const tenantDb = createTenantDb(db, tenantId, "system");
|
|
81
82
|
for (const id of ids) {
|
|
82
83
|
// Race: user may have saved between select and here — skip if no longer stale.
|
|
83
|
-
if (!(await isDraftStillStale(db, id, retentionDays)))
|
|
84
|
+
if (!(await isDraftStillStale(db, tenantId, id, retentionDays))) {
|
|
85
|
+
skippedCount++;
|
|
84
86
|
continue;
|
|
87
|
+
}
|
|
85
88
|
const result = await formDraftExecutor.delete({ id }, systemUser, tenantDb);
|
|
86
89
|
if (result.isSuccess) {
|
|
87
90
|
const row = byId.get(id);
|
|
@@ -93,7 +96,7 @@ async function deleteStaleDraftsBatch(batch, db, retentionDays, log) {
|
|
|
93
96
|
}
|
|
94
97
|
}
|
|
95
98
|
}
|
|
96
|
-
return deletedRows;
|
|
99
|
+
return { deletedRows, skippedCount };
|
|
97
100
|
}
|
|
98
101
|
export async function cleanupDraftsJob(_payload, ctx, db) {
|
|
99
102
|
const resolved = ctx.configResolver
|
|
@@ -122,12 +125,15 @@ export async function cleanupDraftsJob(_payload, ctx, db) {
|
|
|
122
125
|
// entirely for rows with no FileRefs in the blob, the common case.
|
|
123
126
|
// Delete first (with staleness re-check), then release binaries from rows
|
|
124
127
|
// that actually disappeared — never release a draft the user just saved.
|
|
125
|
-
const deletedRows = await deleteStaleDraftsBatch(batch, db, retentionDays, ctx.log);
|
|
128
|
+
const { deletedRows, skippedCount } = await deleteStaleDraftsBatch(batch, db, retentionDays, ctx.log);
|
|
126
129
|
for (const row of deletedRows) {
|
|
127
130
|
await releaseRowFileRefs(row, db, fileProviderResolver, ctx.log);
|
|
128
131
|
}
|
|
129
132
|
deleted += deletedRows.length;
|
|
130
|
-
|
|
133
|
+
// A skipped row is no longer stale, so the next select drops it — a fully
|
|
134
|
+
// skipped batch still makes progress. Only a batch with neither deletes nor
|
|
135
|
+
// skips (all failed) would reselect the same rows forever.
|
|
136
|
+
if (deletedRows.length + skippedCount === 0 || batch.length < DEFAULT_BATCH_SIZE)
|
|
131
137
|
break;
|
|
132
138
|
}
|
|
133
139
|
ctx.log?.info?.(`[form-draft:cleanup] deleted=${deleted} retentionDays=${retentionDays}`);
|
|
@@ -5,6 +5,7 @@ export declare const mailAccountEntity: import("@cosmicdrift/kumiko-types/fields
|
|
|
5
5
|
readonly type: "text";
|
|
6
6
|
readonly description?: string;
|
|
7
7
|
readonly maxLength?: number;
|
|
8
|
+
readonly minLength?: number;
|
|
8
9
|
readonly required?: boolean;
|
|
9
10
|
readonly searchable?: boolean;
|
|
10
11
|
readonly sortable?: boolean;
|
|
@@ -26,6 +27,7 @@ export declare const mailAccountEntity: import("@cosmicdrift/kumiko-types/fields
|
|
|
26
27
|
readonly type: "text";
|
|
27
28
|
readonly description?: string;
|
|
28
29
|
readonly maxLength?: number;
|
|
30
|
+
readonly minLength?: number;
|
|
29
31
|
readonly required?: boolean;
|
|
30
32
|
readonly searchable?: boolean;
|
|
31
33
|
readonly sortable?: boolean;
|
|
@@ -47,6 +49,7 @@ export declare const mailAccountEntity: import("@cosmicdrift/kumiko-types/fields
|
|
|
47
49
|
readonly type: "text";
|
|
48
50
|
readonly description?: string;
|
|
49
51
|
readonly maxLength?: number;
|
|
52
|
+
readonly minLength?: number;
|
|
50
53
|
readonly required?: boolean;
|
|
51
54
|
readonly searchable?: boolean;
|
|
52
55
|
readonly sortable?: boolean;
|
|
@@ -68,6 +71,7 @@ export declare const mailAccountEntity: import("@cosmicdrift/kumiko-types/fields
|
|
|
68
71
|
readonly type: "text";
|
|
69
72
|
readonly description?: string;
|
|
70
73
|
readonly maxLength?: number;
|
|
74
|
+
readonly minLength?: number;
|
|
71
75
|
readonly required?: boolean;
|
|
72
76
|
readonly searchable?: boolean;
|
|
73
77
|
readonly sortable?: boolean;
|
|
@@ -89,6 +93,7 @@ export declare const mailAccountEntity: import("@cosmicdrift/kumiko-types/fields
|
|
|
89
93
|
readonly type: "text";
|
|
90
94
|
readonly description?: string;
|
|
91
95
|
readonly maxLength?: number;
|
|
96
|
+
readonly minLength?: number;
|
|
92
97
|
readonly required?: boolean;
|
|
93
98
|
readonly searchable?: boolean;
|
|
94
99
|
readonly sortable?: boolean;
|
|
@@ -110,6 +115,7 @@ export declare const mailAccountEntity: import("@cosmicdrift/kumiko-types/fields
|
|
|
110
115
|
readonly type: "text";
|
|
111
116
|
readonly description?: string;
|
|
112
117
|
readonly maxLength?: number;
|
|
118
|
+
readonly minLength?: number;
|
|
113
119
|
readonly required?: boolean;
|
|
114
120
|
readonly searchable?: boolean;
|
|
115
121
|
readonly sortable?: boolean;
|
|
@@ -131,6 +137,7 @@ export declare const mailAccountEntity: import("@cosmicdrift/kumiko-types/fields
|
|
|
131
137
|
readonly type: "text";
|
|
132
138
|
readonly description?: string;
|
|
133
139
|
readonly maxLength?: number;
|
|
140
|
+
readonly minLength?: number;
|
|
134
141
|
readonly required?: boolean;
|
|
135
142
|
readonly searchable?: boolean;
|
|
136
143
|
readonly sortable?: boolean;
|
|
@@ -172,6 +179,7 @@ export declare const inboundMessageEntity: import("@cosmicdrift/kumiko-types/fie
|
|
|
172
179
|
readonly type: "text";
|
|
173
180
|
readonly description?: string;
|
|
174
181
|
readonly maxLength?: number;
|
|
182
|
+
readonly minLength?: number;
|
|
175
183
|
readonly required?: boolean;
|
|
176
184
|
readonly searchable?: boolean;
|
|
177
185
|
readonly sortable?: boolean;
|
|
@@ -193,6 +201,7 @@ export declare const inboundMessageEntity: import("@cosmicdrift/kumiko-types/fie
|
|
|
193
201
|
readonly type: "text";
|
|
194
202
|
readonly description?: string;
|
|
195
203
|
readonly maxLength?: number;
|
|
204
|
+
readonly minLength?: number;
|
|
196
205
|
readonly required?: boolean;
|
|
197
206
|
readonly searchable?: boolean;
|
|
198
207
|
readonly sortable?: boolean;
|
|
@@ -214,6 +223,7 @@ export declare const inboundMessageEntity: import("@cosmicdrift/kumiko-types/fie
|
|
|
214
223
|
readonly type: "text";
|
|
215
224
|
readonly description?: string;
|
|
216
225
|
readonly maxLength?: number;
|
|
226
|
+
readonly minLength?: number;
|
|
217
227
|
readonly required?: boolean;
|
|
218
228
|
readonly searchable?: boolean;
|
|
219
229
|
readonly sortable?: boolean;
|
|
@@ -235,6 +245,7 @@ export declare const inboundMessageEntity: import("@cosmicdrift/kumiko-types/fie
|
|
|
235
245
|
readonly type: "text";
|
|
236
246
|
readonly description?: string;
|
|
237
247
|
readonly maxLength?: number;
|
|
248
|
+
readonly minLength?: number;
|
|
238
249
|
readonly required?: boolean;
|
|
239
250
|
readonly searchable?: boolean;
|
|
240
251
|
readonly sortable?: boolean;
|
|
@@ -256,6 +267,7 @@ export declare const inboundMessageEntity: import("@cosmicdrift/kumiko-types/fie
|
|
|
256
267
|
readonly type: "text";
|
|
257
268
|
readonly description?: string;
|
|
258
269
|
readonly maxLength?: number;
|
|
270
|
+
readonly minLength?: number;
|
|
259
271
|
readonly required?: boolean;
|
|
260
272
|
readonly searchable?: boolean;
|
|
261
273
|
readonly sortable?: boolean;
|
|
@@ -277,6 +289,7 @@ export declare const inboundMessageEntity: import("@cosmicdrift/kumiko-types/fie
|
|
|
277
289
|
readonly type: "text";
|
|
278
290
|
readonly description?: string;
|
|
279
291
|
readonly maxLength?: number;
|
|
292
|
+
readonly minLength?: number;
|
|
280
293
|
readonly required?: boolean;
|
|
281
294
|
readonly searchable?: boolean;
|
|
282
295
|
readonly sortable?: boolean;
|
|
@@ -298,6 +311,7 @@ export declare const inboundMessageEntity: import("@cosmicdrift/kumiko-types/fie
|
|
|
298
311
|
readonly type: "text";
|
|
299
312
|
readonly description?: string;
|
|
300
313
|
readonly maxLength?: number;
|
|
314
|
+
readonly minLength?: number;
|
|
301
315
|
readonly required?: boolean;
|
|
302
316
|
readonly searchable?: boolean;
|
|
303
317
|
readonly sortable?: boolean;
|
|
@@ -319,6 +333,7 @@ export declare const inboundMessageEntity: import("@cosmicdrift/kumiko-types/fie
|
|
|
319
333
|
readonly type: "text";
|
|
320
334
|
readonly description?: string;
|
|
321
335
|
readonly maxLength?: number;
|
|
336
|
+
readonly minLength?: number;
|
|
322
337
|
readonly required?: boolean;
|
|
323
338
|
readonly searchable?: boolean;
|
|
324
339
|
readonly sortable?: boolean;
|
|
@@ -340,6 +355,7 @@ export declare const inboundMessageEntity: import("@cosmicdrift/kumiko-types/fie
|
|
|
340
355
|
readonly type: "text";
|
|
341
356
|
readonly description?: string;
|
|
342
357
|
readonly maxLength?: number;
|
|
358
|
+
readonly minLength?: number;
|
|
343
359
|
readonly required?: boolean;
|
|
344
360
|
readonly searchable?: boolean;
|
|
345
361
|
readonly sortable?: boolean;
|
|
@@ -376,6 +392,7 @@ export declare const inboundMessageEntity: import("@cosmicdrift/kumiko-types/fie
|
|
|
376
392
|
readonly type: "text";
|
|
377
393
|
readonly description?: string;
|
|
378
394
|
readonly maxLength?: number;
|
|
395
|
+
readonly minLength?: number;
|
|
379
396
|
readonly required?: boolean;
|
|
380
397
|
readonly searchable?: boolean;
|
|
381
398
|
readonly sortable?: boolean;
|
|
@@ -397,6 +414,7 @@ export declare const inboundMessageEntity: import("@cosmicdrift/kumiko-types/fie
|
|
|
397
414
|
readonly type: "text";
|
|
398
415
|
readonly description?: string;
|
|
399
416
|
readonly maxLength?: number;
|
|
417
|
+
readonly minLength?: number;
|
|
400
418
|
readonly required?: boolean;
|
|
401
419
|
readonly searchable?: boolean;
|
|
402
420
|
readonly sortable?: boolean;
|
|
@@ -422,6 +440,7 @@ export declare const mailThreadEntity: import("@cosmicdrift/kumiko-types/fields"
|
|
|
422
440
|
readonly type: "text";
|
|
423
441
|
readonly description?: string;
|
|
424
442
|
readonly maxLength?: number;
|
|
443
|
+
readonly minLength?: number;
|
|
425
444
|
readonly required?: boolean;
|
|
426
445
|
readonly searchable?: boolean;
|
|
427
446
|
readonly sortable?: boolean;
|
|
@@ -443,6 +462,7 @@ export declare const mailThreadEntity: import("@cosmicdrift/kumiko-types/fields"
|
|
|
443
462
|
readonly type: "text";
|
|
444
463
|
readonly description?: string;
|
|
445
464
|
readonly maxLength?: number;
|
|
465
|
+
readonly minLength?: number;
|
|
446
466
|
readonly required?: boolean;
|
|
447
467
|
readonly searchable?: boolean;
|
|
448
468
|
readonly sortable?: boolean;
|
|
@@ -507,6 +527,7 @@ export declare const syncCursorEntity: import("@cosmicdrift/kumiko-types/fields"
|
|
|
507
527
|
readonly type: "text";
|
|
508
528
|
readonly description?: string;
|
|
509
529
|
readonly maxLength?: number;
|
|
530
|
+
readonly minLength?: number;
|
|
510
531
|
readonly required?: boolean;
|
|
511
532
|
readonly searchable?: boolean;
|
|
512
533
|
readonly sortable?: boolean;
|
|
@@ -528,6 +549,7 @@ export declare const syncCursorEntity: import("@cosmicdrift/kumiko-types/fields"
|
|
|
528
549
|
readonly type: "text";
|
|
529
550
|
readonly description?: string;
|
|
530
551
|
readonly maxLength?: number;
|
|
552
|
+
readonly minLength?: number;
|
|
531
553
|
readonly required?: boolean;
|
|
532
554
|
readonly searchable?: boolean;
|
|
533
555
|
readonly sortable?: boolean;
|
|
@@ -549,6 +571,7 @@ export declare const syncCursorEntity: import("@cosmicdrift/kumiko-types/fields"
|
|
|
549
571
|
readonly type: "text";
|
|
550
572
|
readonly description?: string;
|
|
551
573
|
readonly maxLength?: number;
|
|
574
|
+
readonly minLength?: number;
|
|
552
575
|
readonly required?: boolean;
|
|
553
576
|
readonly searchable?: boolean;
|
|
554
577
|
readonly sortable?: boolean;
|
|
@@ -591,6 +614,7 @@ export declare const seenMessageEntity: import("@cosmicdrift/kumiko-types/fields
|
|
|
591
614
|
readonly type: "text";
|
|
592
615
|
readonly description?: string;
|
|
593
616
|
readonly maxLength?: number;
|
|
617
|
+
readonly minLength?: number;
|
|
594
618
|
readonly required?: boolean;
|
|
595
619
|
readonly searchable?: boolean;
|
|
596
620
|
readonly sortable?: boolean;
|
|
@@ -612,6 +636,7 @@ export declare const seenMessageEntity: import("@cosmicdrift/kumiko-types/fields
|
|
|
612
636
|
readonly type: "text";
|
|
613
637
|
readonly description?: string;
|
|
614
638
|
readonly maxLength?: number;
|
|
639
|
+
readonly minLength?: number;
|
|
615
640
|
readonly required?: boolean;
|
|
616
641
|
readonly searchable?: boolean;
|
|
617
642
|
readonly sortable?: boolean;
|