@cosmicdrift/kumiko-bundled-features 0.383.0 → 0.384.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/audit/event-classification.d.ts +8 -0
- package/dist/audit/event-classification.js +69 -0
- package/dist/audit/feature.js +3 -3
- package/dist/audit/handlers/audit-events.d.ts +25 -3
- package/dist/audit/handlers/audit-events.js +41 -7
- package/dist/audit/handlers/details-all-tenants.query.d.ts +3 -0
- package/dist/audit/handlers/details-all-tenants.query.js +2 -5
- package/dist/audit/handlers/details.query.d.ts +3 -0
- package/dist/audit/handlers/details.query.js +5 -3
- package/dist/audit/handlers/list-all-tenants.query.d.ts +9 -2
- package/dist/audit/handlers/list-all-tenants.query.js +10 -8
- package/dist/audit/handlers/list.query.d.ts +8 -0
- package/dist/audit/handlers/list.query.js +12 -4
- package/dist/audit/screens.d.ts +37 -0
- package/dist/audit/screens.js +26 -4
- package/dist/audit/web/audit-event-cell.d.ts +4 -0
- package/dist/audit/web/audit-event-cell.js +20 -0
- package/dist/audit/web/audit-event-labels.d.ts +7 -0
- package/dist/audit/web/audit-event-labels.js +23 -0
- package/dist/audit/web/client-plugin.js +6 -1
- package/dist/audit/web/constants.d.ts +8 -0
- package/dist/audit/web/constants.js +7 -0
- package/dist/audit/web/i18n.js +48 -1
- package/dist/auth-email-password/feature.js +3 -2
- package/dist/auth-email-password/handlers/login-options.query.d.ts +5 -0
- package/dist/auth-email-password/handlers/login-options.query.js +17 -0
- package/dist/auth-email-password/index.d.ts +1 -1
- package/dist/auth-email-password/index.js +1 -1
- package/dist/auth-email-password/{auth-paths.d.ts → web/auth-paths.d.ts} +5 -3
- package/dist/auth-email-password/{auth-paths.js → web/auth-paths.js} +5 -3
- package/dist/auth-email-password/web/client-plugin.d.ts +5 -0
- package/dist/auth-email-password/web/client-plugin.js +2 -0
- package/dist/auth-email-password/web/constants.d.ts +1 -0
- package/dist/auth-email-password/web/constants.js +1 -0
- package/dist/auth-email-password/web/default-auth-pages-gate.d.ts +4 -0
- package/dist/auth-email-password/web/default-auth-pages-gate.js +28 -0
- package/dist/auth-email-password/web/login-screen.d.ts +13 -18
- package/dist/auth-email-password/web/login-screen.js +31 -19
- package/dist/auth-mfa/schema/user-mfa.d.ts +6 -126
- package/dist/billing-foundation/entities.d.ts +8 -168
- package/dist/billing-foundation/projection.d.ts +8 -168
- package/dist/cap-counter/entity.d.ts +1 -21
- package/dist/compliance-profiles/schema/profile-selection.d.ts +2 -28
- package/dist/config/table.d.ts +6 -126
- package/dist/custom-fields/entity.d.ts +4 -84
- package/dist/data-retention/schema/tenant-retention-override.d.ts +6 -112
- package/dist/delivery/tables.d.ts +6 -126
- package/dist/document-ingest-foundation/entity.d.ts +6 -112
- package/dist/folders/entity.d.ts +8 -168
- package/dist/form-draft/entity.d.ts +2 -42
- package/dist/inbound-mail-foundation/entities.d.ts +25 -525
- package/dist/inbound-mail-foundation/projection.d.ts +20 -420
- package/dist/jobs/feature.js +22 -117
- package/dist/jobs/handlers/consumer.write.d.ts +37 -0
- package/dist/jobs/handlers/consumer.write.js +59 -0
- package/dist/jobs/handlers/consumers.query.d.ts +52 -0
- package/dist/jobs/handlers/consumers.query.js +114 -0
- package/dist/jobs/job-run-table.d.ts +6 -126
- package/dist/jobs/screens.d.ts +6 -0
- package/dist/jobs/screens.js +241 -0
- package/dist/jobs/web/constants.d.ts +20 -0
- package/dist/jobs/web/constants.js +21 -0
- package/dist/jobs/web/i18n.js +41 -0
- package/dist/ledger/entity.d.ts +13 -273
- package/dist/managed-pages/table.d.ts +12 -252
- package/dist/notes-history/entity.d.ts +14 -280
- package/dist/personal-access-tokens/schema/api-token.d.ts +6 -126
- package/dist/secrets/i18n.d.ts +0 -3
- package/dist/secrets/i18n.js +0 -3
- package/dist/secrets/table.d.ts +1 -21
- package/dist/sessions/constants.d.ts +2 -1
- package/dist/sessions/constants.js +2 -1
- package/dist/sessions/handlers/detail.query.d.ts +5 -0
- package/dist/sessions/handlers/detail.query.js +7 -3
- package/dist/sessions/handlers/list.query.d.ts +5 -0
- package/dist/sessions/handlers/list.query.js +16 -10
- package/dist/sessions/handlers/mine.query.d.ts +5 -0
- package/dist/sessions/handlers/mine.query.js +15 -9
- package/dist/sessions/schema/user-session.d.ts +4 -84
- package/dist/sessions/screens.js +22 -8
- package/dist/sessions/web/client-plugin.d.ts +6 -0
- package/dist/sessions/web/client-plugin.js +11 -0
- package/dist/sessions/web/constants.d.ts +3 -0
- package/dist/sessions/web/constants.js +3 -0
- package/dist/sessions/web/default-translations.d.ts +2 -0
- package/dist/sessions/web/default-translations.js +3 -0
- package/dist/sessions/web/i18n.js +5 -1
- package/dist/sessions/web/index.d.ts +1 -0
- package/dist/sessions/web/index.js +1 -0
- package/dist/sessions/web/session-device-cell.d.ts +3 -0
- package/dist/sessions/web/session-device-cell.js +16 -0
- package/dist/sessions/web/user-agent.d.ts +26 -0
- package/dist/sessions/web/user-agent.js +58 -0
- package/dist/tags/entity.d.ts +9 -189
- package/dist/template-resolver/feature.d.ts +1 -0
- package/dist/template-resolver/feature.js +2 -0
- package/dist/template-resolver/handlers/overview.query.d.ts +5 -0
- package/dist/template-resolver/handlers/overview.query.js +19 -2
- package/dist/template-resolver/handlers/tenant-override-counts.query.d.ts +17 -0
- package/dist/template-resolver/handlers/tenant-override-counts.query.js +35 -0
- package/dist/template-resolver/handlers/tenant-override-selection.d.ts +12 -0
- package/dist/template-resolver/handlers/tenant-override-selection.js +34 -0
- package/dist/template-resolver/handlers/tenant-overrides.query.js +3 -29
- package/dist/template-resolver/table.d.ts +20 -350
- package/dist/template-resolver/user-content-table.d.ts +12 -238
- package/dist/template-resolver/web/qualified-names.d.ts +1 -0
- package/dist/template-resolver/web/qualified-names.js +1 -0
- package/dist/tenant/invitation-table.d.ts +6 -126
- package/dist/tenant/membership-table.d.ts +4 -84
- package/dist/tenant/schema/tenant.d.ts +6 -126
- package/dist/tier-engine/entity.d.ts +1 -21
- package/dist/tier-engine/tier-resolver.d.ts +1 -21
- package/dist/user/schema/user.d.ts +12 -252
- package/dist/user-data-rights/feature.js +33 -33
- package/dist/user-data-rights/schema/download-attempt.d.ts +14 -294
- package/dist/user-data-rights/schema/download-token.d.ts +8 -168
- package/dist/user-data-rights/schema/export-job.d.ts +8 -154
- package/dist/user-data-rights/web/i18n.js +4 -1
- package/dist/waitlist/entity.d.ts +16 -322
- package/package.json +17 -9
- package/src/audit/changes.json +6 -0
- package/src/auth-email-password/changes.json +6 -0
- package/src/jobs/changes.json +6 -0
- package/src/sessions/changes.json +6 -0
- package/src/template-resolver/changes.json +6 -0
- package/src/user-data-rights/changes.json +18 -0
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import * as z from "zod";
|
|
2
|
+
export declare const USER_AGENT_BROWSERS: readonly ["Edge", "Opera", "Samsung Internet", "Firefox", "Chrome", "Safari"];
|
|
3
|
+
export declare const USER_AGENT_OPERATING_SYSTEMS: readonly ["Windows", "iOS", "Android", "macOS", "ChromeOS", "Linux"];
|
|
4
|
+
export type UserAgentBrowser = (typeof USER_AGENT_BROWSERS)[number];
|
|
5
|
+
export type UserAgentOperatingSystem = (typeof USER_AGENT_OPERATING_SYSTEMS)[number];
|
|
6
|
+
export declare const parsedDeviceSchema: z.ZodNullable<z.ZodObject<{
|
|
7
|
+
browser: z.ZodNullable<z.ZodEnum<{
|
|
8
|
+
Chrome: "Chrome";
|
|
9
|
+
Edge: "Edge";
|
|
10
|
+
Firefox: "Firefox";
|
|
11
|
+
Opera: "Opera";
|
|
12
|
+
Safari: "Safari";
|
|
13
|
+
"Samsung Internet": "Samsung Internet";
|
|
14
|
+
}>>;
|
|
15
|
+
browserMajor: z.ZodNullable<z.ZodNumber>;
|
|
16
|
+
os: z.ZodNullable<z.ZodEnum<{
|
|
17
|
+
Android: "Android";
|
|
18
|
+
ChromeOS: "ChromeOS";
|
|
19
|
+
Linux: "Linux";
|
|
20
|
+
Windows: "Windows";
|
|
21
|
+
iOS: "iOS";
|
|
22
|
+
macOS: "macOS";
|
|
23
|
+
}>>;
|
|
24
|
+
}, z.core.$strip>>;
|
|
25
|
+
export type ParsedDevice = z.infer<typeof parsedDeviceSchema>;
|
|
26
|
+
export declare function parseUserAgent(userAgent: string | null): ParsedDevice;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import * as z from "zod";
|
|
2
|
+
export const USER_AGENT_BROWSERS = [
|
|
3
|
+
"Edge",
|
|
4
|
+
"Opera",
|
|
5
|
+
"Samsung Internet",
|
|
6
|
+
"Firefox",
|
|
7
|
+
"Chrome",
|
|
8
|
+
"Safari",
|
|
9
|
+
];
|
|
10
|
+
export const USER_AGENT_OPERATING_SYSTEMS = [
|
|
11
|
+
"Windows",
|
|
12
|
+
"iOS",
|
|
13
|
+
"Android",
|
|
14
|
+
"macOS",
|
|
15
|
+
"ChromeOS",
|
|
16
|
+
"Linux",
|
|
17
|
+
];
|
|
18
|
+
export const parsedDeviceSchema = z
|
|
19
|
+
.object({
|
|
20
|
+
browser: z.enum(USER_AGENT_BROWSERS).nullable(),
|
|
21
|
+
browserMajor: z.number().int().nullable(),
|
|
22
|
+
os: z.enum(USER_AGENT_OPERATING_SYSTEMS).nullable(),
|
|
23
|
+
})
|
|
24
|
+
.nullable();
|
|
25
|
+
// Order matters: Chromium forks and iOS Chrome/Firefox also carry the engine tokens of the browsers they derive from.
|
|
26
|
+
const BROWSER_PATTERNS = [
|
|
27
|
+
{ browser: "Edge", pattern: /\bEdg(?:e|A|iOS)?\/(\d+)/ },
|
|
28
|
+
{ browser: "Opera", pattern: /\bOPR\/(\d+)/ },
|
|
29
|
+
{ browser: "Samsung Internet", pattern: /\bSamsungBrowser\/(\d+)/ },
|
|
30
|
+
{ browser: "Firefox", pattern: /\b(?:Firefox|FxiOS)\/(\d+)/ },
|
|
31
|
+
{ browser: "Chrome", pattern: /\b(?:Chrome|CriOS)\/(\d+)/ },
|
|
32
|
+
{ browser: "Safari", pattern: /\bVersion\/(\d+)[\s\S]*\bSafari\// },
|
|
33
|
+
];
|
|
34
|
+
// iPhone/iPad UAs say "like Mac OS X" and Android UAs say "Linux", so the more specific systems come first.
|
|
35
|
+
const OPERATING_SYSTEM_PATTERNS = [
|
|
36
|
+
{ os: "Windows", pattern: /\bWindows\b/ },
|
|
37
|
+
{ os: "iOS", pattern: /\b(?:iPhone|iPad|iPod)\b/ },
|
|
38
|
+
{ os: "Android", pattern: /\bAndroid\b/ },
|
|
39
|
+
{ os: "macOS", pattern: /\bMacintosh\b/ },
|
|
40
|
+
{ os: "ChromeOS", pattern: /\bCrOS\b/ },
|
|
41
|
+
{ os: "Linux", pattern: /\bLinux\b/ },
|
|
42
|
+
];
|
|
43
|
+
export function parseUserAgent(userAgent) {
|
|
44
|
+
if (userAgent === null || userAgent === "")
|
|
45
|
+
return null;
|
|
46
|
+
let browser = null;
|
|
47
|
+
let browserMajor = null;
|
|
48
|
+
for (const candidate of BROWSER_PATTERNS) {
|
|
49
|
+
const match = candidate.pattern.exec(userAgent);
|
|
50
|
+
if (match) {
|
|
51
|
+
browser = candidate.browser;
|
|
52
|
+
browserMajor = match[1] === undefined ? null : Number(match[1]);
|
|
53
|
+
break;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
const os = OPERATING_SYSTEM_PATTERNS.find((candidate) => candidate.pattern.test(userAgent))?.os ?? null;
|
|
57
|
+
return browser === null && os === null ? null : { browser, browserMajor, os };
|
|
58
|
+
}
|
package/dist/tags/entity.d.ts
CHANGED
|
@@ -1,214 +1,34 @@
|
|
|
1
1
|
import { type EntityDefinition } from "@cosmicdrift/kumiko-framework/engine";
|
|
2
2
|
export declare const tagEntity: EntityDefinition<{
|
|
3
|
-
readonly name: {
|
|
4
|
-
readonly type: "text";
|
|
5
|
-
readonly description?: string;
|
|
6
|
-
readonly maxLength?: number;
|
|
7
|
-
readonly minLength?: number;
|
|
8
|
-
readonly autoComplete?: string;
|
|
9
|
-
readonly required?: boolean;
|
|
10
|
-
readonly searchable?: boolean;
|
|
11
|
-
readonly sortable?: boolean;
|
|
12
|
-
readonly filterable?: boolean;
|
|
13
|
-
readonly encrypted?: boolean;
|
|
14
|
-
readonly sensitive?: boolean;
|
|
15
|
-
readonly writeOnly?: boolean;
|
|
16
|
-
readonly readAsInstruction?: boolean;
|
|
17
|
-
readonly format?: "email" | "url" | "phone" | "password" | "month" | "iban";
|
|
18
|
-
readonly default?: string;
|
|
19
|
-
readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
|
|
20
|
-
readonly multiline?: boolean | {
|
|
21
|
-
readonly rows?: number;
|
|
22
|
-
};
|
|
23
|
-
} & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
|
|
3
|
+
readonly name: import("@cosmicdrift/kumiko-types/fields").EntityTextFieldDef & {
|
|
24
4
|
required: true;
|
|
25
5
|
};
|
|
26
|
-
readonly color: {
|
|
27
|
-
readonly type: "text";
|
|
28
|
-
readonly description?: string;
|
|
29
|
-
readonly maxLength?: number;
|
|
30
|
-
readonly minLength?: number;
|
|
31
|
-
readonly autoComplete?: string;
|
|
32
|
-
readonly required?: boolean;
|
|
33
|
-
readonly searchable?: boolean;
|
|
34
|
-
readonly sortable?: boolean;
|
|
35
|
-
readonly filterable?: boolean;
|
|
36
|
-
readonly encrypted?: boolean;
|
|
37
|
-
readonly sensitive?: boolean;
|
|
38
|
-
readonly writeOnly?: boolean;
|
|
39
|
-
readonly readAsInstruction?: boolean;
|
|
40
|
-
readonly format?: "email" | "url" | "phone" | "password" | "month" | "iban";
|
|
41
|
-
readonly default?: string;
|
|
42
|
-
readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
|
|
43
|
-
readonly multiline?: boolean | {
|
|
44
|
-
readonly rows?: number;
|
|
45
|
-
};
|
|
46
|
-
} & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
|
|
6
|
+
readonly color: import("@cosmicdrift/kumiko-types/fields").EntityTextFieldDef & {
|
|
47
7
|
required: false;
|
|
48
8
|
};
|
|
49
|
-
readonly scope: {
|
|
50
|
-
readonly type: "text";
|
|
51
|
-
readonly description?: string;
|
|
52
|
-
readonly maxLength?: number;
|
|
53
|
-
readonly minLength?: number;
|
|
54
|
-
readonly autoComplete?: string;
|
|
55
|
-
readonly required?: boolean;
|
|
56
|
-
readonly searchable?: boolean;
|
|
57
|
-
readonly sortable?: boolean;
|
|
58
|
-
readonly filterable?: boolean;
|
|
59
|
-
readonly encrypted?: boolean;
|
|
60
|
-
readonly sensitive?: boolean;
|
|
61
|
-
readonly writeOnly?: boolean;
|
|
62
|
-
readonly readAsInstruction?: boolean;
|
|
63
|
-
readonly format?: "email" | "url" | "phone" | "password" | "month" | "iban";
|
|
64
|
-
readonly default?: string;
|
|
65
|
-
readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
|
|
66
|
-
readonly multiline?: boolean | {
|
|
67
|
-
readonly rows?: number;
|
|
68
|
-
};
|
|
69
|
-
} & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
|
|
9
|
+
readonly scope: import("@cosmicdrift/kumiko-types/fields").EntityTextFieldDef & {
|
|
70
10
|
required: false;
|
|
71
11
|
};
|
|
72
12
|
}, "tenant">;
|
|
73
13
|
export declare function createTagAssignmentEntity(access?: EntityDefinition["access"], parents?: readonly string[]): EntityDefinition<{
|
|
74
|
-
readonly tagId: {
|
|
75
|
-
readonly type: "text";
|
|
76
|
-
readonly description?: string;
|
|
77
|
-
readonly maxLength?: number;
|
|
78
|
-
readonly minLength?: number;
|
|
79
|
-
readonly autoComplete?: string;
|
|
80
|
-
readonly required?: boolean;
|
|
81
|
-
readonly searchable?: boolean;
|
|
82
|
-
readonly sortable?: boolean;
|
|
83
|
-
readonly filterable?: boolean;
|
|
84
|
-
readonly encrypted?: boolean;
|
|
85
|
-
readonly sensitive?: boolean;
|
|
86
|
-
readonly writeOnly?: boolean;
|
|
87
|
-
readonly readAsInstruction?: boolean;
|
|
88
|
-
readonly format?: "email" | "url" | "phone" | "password" | "month" | "iban";
|
|
89
|
-
readonly default?: string;
|
|
90
|
-
readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
|
|
91
|
-
readonly multiline?: boolean | {
|
|
92
|
-
readonly rows?: number;
|
|
93
|
-
};
|
|
94
|
-
} & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
|
|
14
|
+
readonly tagId: import("@cosmicdrift/kumiko-types/fields").EntityTextFieldDef & {
|
|
95
15
|
required: true;
|
|
96
16
|
};
|
|
97
|
-
readonly entityType: {
|
|
98
|
-
readonly type: "text";
|
|
99
|
-
readonly description?: string;
|
|
100
|
-
readonly maxLength?: number;
|
|
101
|
-
readonly minLength?: number;
|
|
102
|
-
readonly autoComplete?: string;
|
|
103
|
-
readonly required?: boolean;
|
|
104
|
-
readonly searchable?: boolean;
|
|
105
|
-
readonly sortable?: boolean;
|
|
106
|
-
readonly filterable?: boolean;
|
|
107
|
-
readonly encrypted?: boolean;
|
|
108
|
-
readonly sensitive?: boolean;
|
|
109
|
-
readonly writeOnly?: boolean;
|
|
110
|
-
readonly readAsInstruction?: boolean;
|
|
111
|
-
readonly format?: "email" | "url" | "phone" | "password" | "month" | "iban";
|
|
112
|
-
readonly default?: string;
|
|
113
|
-
readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
|
|
114
|
-
readonly multiline?: boolean | {
|
|
115
|
-
readonly rows?: number;
|
|
116
|
-
};
|
|
117
|
-
} & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
|
|
17
|
+
readonly entityType: import("@cosmicdrift/kumiko-types/fields").EntityTextFieldDef & {
|
|
118
18
|
required: true;
|
|
119
19
|
};
|
|
120
|
-
readonly entityId: {
|
|
121
|
-
readonly type: "text";
|
|
122
|
-
readonly description?: string;
|
|
123
|
-
readonly maxLength?: number;
|
|
124
|
-
readonly minLength?: number;
|
|
125
|
-
readonly autoComplete?: string;
|
|
126
|
-
readonly required?: boolean;
|
|
127
|
-
readonly searchable?: boolean;
|
|
128
|
-
readonly sortable?: boolean;
|
|
129
|
-
readonly filterable?: boolean;
|
|
130
|
-
readonly encrypted?: boolean;
|
|
131
|
-
readonly sensitive?: boolean;
|
|
132
|
-
readonly writeOnly?: boolean;
|
|
133
|
-
readonly readAsInstruction?: boolean;
|
|
134
|
-
readonly format?: "email" | "url" | "phone" | "password" | "month" | "iban";
|
|
135
|
-
readonly default?: string;
|
|
136
|
-
readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
|
|
137
|
-
readonly multiline?: boolean | {
|
|
138
|
-
readonly rows?: number;
|
|
139
|
-
};
|
|
140
|
-
} & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
|
|
20
|
+
readonly entityId: import("@cosmicdrift/kumiko-types/fields").EntityTextFieldDef & {
|
|
141
21
|
required: true;
|
|
142
22
|
};
|
|
143
23
|
}, "tenant">;
|
|
144
24
|
export declare const tagAssignmentEntity: EntityDefinition<{
|
|
145
|
-
readonly tagId: {
|
|
146
|
-
readonly type: "text";
|
|
147
|
-
readonly description?: string;
|
|
148
|
-
readonly maxLength?: number;
|
|
149
|
-
readonly minLength?: number;
|
|
150
|
-
readonly autoComplete?: string;
|
|
151
|
-
readonly required?: boolean;
|
|
152
|
-
readonly searchable?: boolean;
|
|
153
|
-
readonly sortable?: boolean;
|
|
154
|
-
readonly filterable?: boolean;
|
|
155
|
-
readonly encrypted?: boolean;
|
|
156
|
-
readonly sensitive?: boolean;
|
|
157
|
-
readonly writeOnly?: boolean;
|
|
158
|
-
readonly readAsInstruction?: boolean;
|
|
159
|
-
readonly format?: "email" | "url" | "phone" | "password" | "month" | "iban";
|
|
160
|
-
readonly default?: string;
|
|
161
|
-
readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
|
|
162
|
-
readonly multiline?: boolean | {
|
|
163
|
-
readonly rows?: number;
|
|
164
|
-
};
|
|
165
|
-
} & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
|
|
25
|
+
readonly tagId: import("@cosmicdrift/kumiko-types/fields").EntityTextFieldDef & {
|
|
166
26
|
required: true;
|
|
167
27
|
};
|
|
168
|
-
readonly entityType: {
|
|
169
|
-
readonly type: "text";
|
|
170
|
-
readonly description?: string;
|
|
171
|
-
readonly maxLength?: number;
|
|
172
|
-
readonly minLength?: number;
|
|
173
|
-
readonly autoComplete?: string;
|
|
174
|
-
readonly required?: boolean;
|
|
175
|
-
readonly searchable?: boolean;
|
|
176
|
-
readonly sortable?: boolean;
|
|
177
|
-
readonly filterable?: boolean;
|
|
178
|
-
readonly encrypted?: boolean;
|
|
179
|
-
readonly sensitive?: boolean;
|
|
180
|
-
readonly writeOnly?: boolean;
|
|
181
|
-
readonly readAsInstruction?: boolean;
|
|
182
|
-
readonly format?: "email" | "url" | "phone" | "password" | "month" | "iban";
|
|
183
|
-
readonly default?: string;
|
|
184
|
-
readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
|
|
185
|
-
readonly multiline?: boolean | {
|
|
186
|
-
readonly rows?: number;
|
|
187
|
-
};
|
|
188
|
-
} & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
|
|
28
|
+
readonly entityType: import("@cosmicdrift/kumiko-types/fields").EntityTextFieldDef & {
|
|
189
29
|
required: true;
|
|
190
30
|
};
|
|
191
|
-
readonly entityId: {
|
|
192
|
-
readonly type: "text";
|
|
193
|
-
readonly description?: string;
|
|
194
|
-
readonly maxLength?: number;
|
|
195
|
-
readonly minLength?: number;
|
|
196
|
-
readonly autoComplete?: string;
|
|
197
|
-
readonly required?: boolean;
|
|
198
|
-
readonly searchable?: boolean;
|
|
199
|
-
readonly sortable?: boolean;
|
|
200
|
-
readonly filterable?: boolean;
|
|
201
|
-
readonly encrypted?: boolean;
|
|
202
|
-
readonly sensitive?: boolean;
|
|
203
|
-
readonly writeOnly?: boolean;
|
|
204
|
-
readonly readAsInstruction?: boolean;
|
|
205
|
-
readonly format?: "email" | "url" | "phone" | "password" | "month" | "iban";
|
|
206
|
-
readonly default?: string;
|
|
207
|
-
readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
|
|
208
|
-
readonly multiline?: boolean | {
|
|
209
|
-
readonly rows?: number;
|
|
210
|
-
};
|
|
211
|
-
} & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
|
|
31
|
+
readonly entityId: import("@cosmicdrift/kumiko-types/fields").EntityTextFieldDef & {
|
|
212
32
|
required: true;
|
|
213
33
|
};
|
|
214
34
|
}, "tenant">;
|
|
@@ -51,6 +51,7 @@ export declare function createTemplateResolverFeature(opts?: TemplateResolverOpt
|
|
|
51
51
|
overview: import("@cosmicdrift/kumiko-types/handlers").HandlerRef;
|
|
52
52
|
layoutSource: import("@cosmicdrift/kumiko-types/handlers").HandlerRef;
|
|
53
53
|
tenantOverrides: import("@cosmicdrift/kumiko-types/handlers").HandlerRef;
|
|
54
|
+
tenantOverrideCounts: import("@cosmicdrift/kumiko-types/handlers").HandlerRef;
|
|
54
55
|
diffToSystem: import("@cosmicdrift/kumiko-types/handlers").HandlerRef;
|
|
55
56
|
validate: import("@cosmicdrift/kumiko-types/handlers").HandlerRef;
|
|
56
57
|
preview: import("@cosmicdrift/kumiko-types/handlers").HandlerRef;
|
|
@@ -24,6 +24,7 @@ import { makeResourceUploadWrite } from "./handlers/resource-upload.write.js";
|
|
|
24
24
|
import { makeSendTestWrite } from "./handlers/send-test.write.js";
|
|
25
25
|
import { sendTestStatusQuery } from "./handlers/send-test-status.query.js";
|
|
26
26
|
import { makeSetWrite } from "./handlers/set.write.js";
|
|
27
|
+
import { tenantOverrideCountsQuery } from "./handlers/tenant-override-counts.query.js";
|
|
27
28
|
import { tenantOverridesQuery } from "./handlers/tenant-overrides.query.js";
|
|
28
29
|
import { makeArchiveSystemWrite, makeArchiveWrite, makePublishSystemWrite, makePublishWrite, } from "./handlers/toggle-status.write.js";
|
|
29
30
|
import { upsertSystemWrite } from "./handlers/upsert-system.write.js";
|
|
@@ -130,6 +131,7 @@ export function createTemplateResolverFeature(opts = {}) {
|
|
|
130
131
|
overview: r.queryHandler(makeOverviewQuery(policy)),
|
|
131
132
|
layoutSource: r.queryHandler(layoutSourceQuery),
|
|
132
133
|
tenantOverrides: r.queryHandler(tenantOverridesQuery),
|
|
134
|
+
tenantOverrideCounts: r.queryHandler(tenantOverrideCountsQuery),
|
|
133
135
|
diffToSystem: r.queryHandler(diffToSystemQuery),
|
|
134
136
|
validate: r.queryHandler(validateQuery),
|
|
135
137
|
preview: r.queryHandler(previewQuery),
|
|
@@ -38,6 +38,11 @@ export type OverviewRow = {
|
|
|
38
38
|
readonly modifiedAt: string;
|
|
39
39
|
readonly usedBy: readonly OverviewUsage[];
|
|
40
40
|
readonly usedByCount: number;
|
|
41
|
+
/** SystemAdmin only, system-origin rows: tenants overriding this template; total is null without the tenant feature. */
|
|
42
|
+
readonly overriddenBy?: {
|
|
43
|
+
readonly active: number;
|
|
44
|
+
readonly total: number | null;
|
|
45
|
+
};
|
|
41
46
|
};
|
|
42
47
|
export type OverviewResponse = {
|
|
43
48
|
readonly rows: readonly OverviewRow[];
|
|
@@ -3,12 +3,14 @@ import { defineQueryHandler } from "@cosmicdrift/kumiko-framework/engine";
|
|
|
3
3
|
import * as z from "zod";
|
|
4
4
|
import { templateResourcesTable } from "../table.js";
|
|
5
5
|
import { deliveryPolicyOf, isTenantOverrideServed, } from "../template-delivery-policy.js";
|
|
6
|
-
import { activeSinceOf, activeVersionOf, draftViewerTenantId, isHiddenSystemDraft, parseTemplateDraft, servedVersionOf, } from "../template-draft.js";
|
|
6
|
+
import { activeSinceOf, activeVersionOf, draftViewerTenantId, isHiddenSystemDraft, isSystemAdmin, parseTemplateDraft, servedVersionOf, } from "../template-draft.js";
|
|
7
7
|
import { mayEditTemplate } from "../template-edit-policy.js";
|
|
8
8
|
import { isSystemOnlyTemplateHiddenFrom } from "../template-system-only.js";
|
|
9
9
|
import { collectTemplateUsage } from "../template-usage.js";
|
|
10
10
|
import { DEFAULT_MAIL_LAYOUT_SLUG, FALLBACK_LOCALE, } from "../web/constants.js";
|
|
11
|
+
import { TemplateResolverQueries } from "../web/qualified-names.js";
|
|
11
12
|
import { storedRecord } from "./template-gates.js";
|
|
13
|
+
import { tenantOverrideCountsResultSchema } from "./tenant-override-counts.query.js";
|
|
12
14
|
const OVERVIEW_KINDS = ["mail", "document-pdf", "mail-layout", "pdf-layout"];
|
|
13
15
|
const OVERVIEW_ROW_LIMIT = 5000;
|
|
14
16
|
const LOCKED_OVERRIDE_I18N_KEY = "templateResolver.overview.lockedOverride";
|
|
@@ -188,6 +190,18 @@ function withUsage(draft, layoutSlugByDocument, registry, effectiveLayouts) {
|
|
|
188
190
|
}
|
|
189
191
|
return { ...draft, usedBy, usedByCount: usedBy.length };
|
|
190
192
|
}
|
|
193
|
+
function withOverriddenBy(rows, overrides) {
|
|
194
|
+
const activeByKey = new Map(overrides.counts.map((entry) => [`${entry.kind}|${entry.slug}`, entry.activeOverrides]));
|
|
195
|
+
return rows.map((row) => row.origin === "system"
|
|
196
|
+
? {
|
|
197
|
+
...row,
|
|
198
|
+
overriddenBy: {
|
|
199
|
+
active: activeByKey.get(`${row.kind}|${row.slug}`) ?? 0,
|
|
200
|
+
total: overrides.totalTenants,
|
|
201
|
+
},
|
|
202
|
+
}
|
|
203
|
+
: row);
|
|
204
|
+
}
|
|
191
205
|
function filterRows(rows, search, filters, layoutSlug) {
|
|
192
206
|
const needle = search?.trim().toLowerCase() ?? "";
|
|
193
207
|
return rows.filter((row) => {
|
|
@@ -234,7 +248,10 @@ export function makeOverviewQuery(policy) {
|
|
|
234
248
|
const deliveryPolicy = deliveryPolicyOf(ctx.registry);
|
|
235
249
|
const { drafts, layoutSlugByDocument } = buildDrafts(groupBySlugAndKind(visibleRows), query.user, deliveryPolicy);
|
|
236
250
|
const effectiveLayouts = effectiveMailLayoutSlugs(visibleRows, deliveryPolicy);
|
|
237
|
-
const
|
|
251
|
+
const rowsWithUsage = drafts.map((draft) => withUsage(draft, layoutSlugByDocument, ctx.registry, effectiveLayouts));
|
|
252
|
+
const overviewRows = isSystemAdmin(query.user)
|
|
253
|
+
? withOverriddenBy(rowsWithUsage, tenantOverrideCountsResultSchema.parse(await ctx.query(TemplateResolverQueries.tenantOverrideCounts, {})))
|
|
254
|
+
: rowsWithUsage;
|
|
238
255
|
const filtered = filterRows(overviewRows, payload.search, payload.filters ?? [], payload.layoutSlug);
|
|
239
256
|
const sortField = payload.sort !== undefined && isSortField(payload.sort) ? payload.sort : "modifiedAt";
|
|
240
257
|
const direction = (payload.sortDirection ?? "desc") === "desc" ? -1 : 1;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import * as z from "zod";
|
|
2
|
+
export declare const tenantOverrideCountsResultSchema: z.ZodObject<{
|
|
3
|
+
totalTenants: z.ZodNullable<z.ZodNumber>;
|
|
4
|
+
counts: z.ZodArray<z.ZodObject<{
|
|
5
|
+
slug: z.ZodString;
|
|
6
|
+
kind: z.ZodString;
|
|
7
|
+
activeOverrides: z.ZodNumber;
|
|
8
|
+
}, z.core.$strip>>;
|
|
9
|
+
}, z.core.$strip>;
|
|
10
|
+
export declare const tenantOverrideCountsQuery: import("@cosmicdrift/kumiko-types/define-handler").QueryHandlerDefinition<"tenant-override-counts", z.ZodObject<{}, z.core.$strip>, {
|
|
11
|
+
totalTenants: number | null;
|
|
12
|
+
counts: {
|
|
13
|
+
slug: string;
|
|
14
|
+
kind: string;
|
|
15
|
+
activeOverrides: number;
|
|
16
|
+
}[];
|
|
17
|
+
}, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap>;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { selectMany } from "@cosmicdrift/kumiko-framework/db";
|
|
2
|
+
import { defineQueryHandler } from "@cosmicdrift/kumiko-framework/engine";
|
|
3
|
+
import * as z from "zod";
|
|
4
|
+
import { templateResourcesTable } from "../table.js";
|
|
5
|
+
import { loadEnabledTenantIds, pickBestRowPerTenant, TENANT_OVERRIDES_REASON, } from "./tenant-override-selection.js";
|
|
6
|
+
export const tenantOverrideCountsResultSchema = z.object({
|
|
7
|
+
totalTenants: z.number().nullable(),
|
|
8
|
+
counts: z.array(z.object({ slug: z.string(), kind: z.string(), activeOverrides: z.number() })),
|
|
9
|
+
});
|
|
10
|
+
export const tenantOverrideCountsQuery = defineQueryHandler({
|
|
11
|
+
name: "tenant-override-counts",
|
|
12
|
+
schema: z.object({}),
|
|
13
|
+
access: { roles: ["SystemAdmin"] },
|
|
14
|
+
description: "Counts, per template slug and kind, the enabled tenants that override it with an active version, next to the number of enabled tenants; one query for the whole template overview instead of one tenant-overrides call per row.",
|
|
15
|
+
escapeHatch: { grants: ["unsafeRaw"], reason: TENANT_OVERRIDES_REASON },
|
|
16
|
+
handler: async (_query, ctx) => {
|
|
17
|
+
const found = await selectMany(ctx.db.unsafeRaw(), templateResourcesTable, {
|
|
18
|
+
scope: "tenant",
|
|
19
|
+
});
|
|
20
|
+
const enabledTenantIds = await loadEnabledTenantIds(ctx);
|
|
21
|
+
const rowsBySlugAndKind = new Map();
|
|
22
|
+
for (const row of found) {
|
|
23
|
+
const key = `${row.kind}|${row.slug}`;
|
|
24
|
+
const group = rowsBySlugAndKind.get(key) ?? { slug: row.slug, kind: row.kind, rows: [] };
|
|
25
|
+
group.rows.push(row);
|
|
26
|
+
rowsBySlugAndKind.set(key, group);
|
|
27
|
+
}
|
|
28
|
+
const counts = [...rowsBySlugAndKind.values()].map(({ slug, kind, rows }) => ({
|
|
29
|
+
slug,
|
|
30
|
+
kind,
|
|
31
|
+
activeOverrides: [...pickBestRowPerTenant(rows, enabledTenantIds).values()].filter((row) => row.status === "active").length,
|
|
32
|
+
}));
|
|
33
|
+
return { totalTenants: enabledTenantIds === null ? null : enabledTenantIds.size, counts };
|
|
34
|
+
},
|
|
35
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { TemplateResourceRow } from "../table.ts";
|
|
2
|
+
export declare const TENANT_OVERRIDES_REASON = "SystemAdmin template overview lists which tenants override one system template; it reads tenant-scope template rows of every tenant (and the system row for the optional per-tenant diff), which the tenant-scoped db cannot reach";
|
|
3
|
+
type TenantIdQueryContext = {
|
|
4
|
+
readonly registry: {
|
|
5
|
+
getQueryHandler(name: string): unknown;
|
|
6
|
+
};
|
|
7
|
+
readonly query: (name: string, payload: Record<string, never>) => Promise<unknown>;
|
|
8
|
+
};
|
|
9
|
+
/** Null when the tenant feature is not installed: every tenant row then counts. */
|
|
10
|
+
export declare function loadEnabledTenantIds(ctx: TenantIdQueryContext): Promise<ReadonlySet<string> | null>;
|
|
11
|
+
export declare function pickBestRowPerTenant(found: readonly TemplateResourceRow[], enabledTenantIds: ReadonlySet<string> | null): Map<string, TemplateResourceRow>;
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { SYSTEM_TENANT_ID } from "@cosmicdrift/kumiko-framework/engine";
|
|
2
|
+
import * as z from "zod";
|
|
3
|
+
import { TenantQueries } from "../../tenant/index.js";
|
|
4
|
+
export const TENANT_OVERRIDES_REASON = "SystemAdmin template overview lists which tenants override one system template; it reads tenant-scope template rows of every tenant (and the system row for the optional per-tenant diff), which the tenant-scoped db cannot reach";
|
|
5
|
+
const tenantIdsSchema = z.array(z.string());
|
|
6
|
+
/** Null when the tenant feature is not installed: every tenant row then counts. */
|
|
7
|
+
export async function loadEnabledTenantIds(ctx) {
|
|
8
|
+
if (ctx.registry.getQueryHandler(TenantQueries.activeTenantIds) === undefined)
|
|
9
|
+
return null;
|
|
10
|
+
return new Set(tenantIdsSchema.parse(await ctx.query(TenantQueries.activeTenantIds, {})));
|
|
11
|
+
}
|
|
12
|
+
// One row per tenant: its active override wins over a draft, then the newest version.
|
|
13
|
+
function isBetterRow(candidate, current) {
|
|
14
|
+
if ((candidate.status === "active") !== (current.status === "active")) {
|
|
15
|
+
return candidate.status === "active";
|
|
16
|
+
}
|
|
17
|
+
return candidate.version > current.version;
|
|
18
|
+
}
|
|
19
|
+
function isCountedTenantRow(row, enabledTenantIds) {
|
|
20
|
+
if (row.tenantId === SYSTEM_TENANT_ID || row.status === "archived")
|
|
21
|
+
return false;
|
|
22
|
+
return enabledTenantIds === null || enabledTenantIds.has(row.tenantId);
|
|
23
|
+
}
|
|
24
|
+
export function pickBestRowPerTenant(found, enabledTenantIds) {
|
|
25
|
+
const bestByTenant = new Map();
|
|
26
|
+
for (const row of found) {
|
|
27
|
+
if (!isCountedTenantRow(row, enabledTenantIds))
|
|
28
|
+
continue;
|
|
29
|
+
const current = bestByTenant.get(row.tenantId);
|
|
30
|
+
if (current === undefined || isBetterRow(row, current))
|
|
31
|
+
bestByTenant.set(row.tenantId, row);
|
|
32
|
+
}
|
|
33
|
+
return bestByTenant;
|
|
34
|
+
}
|
|
@@ -6,40 +6,16 @@ import { templateResourcesTable } from "../table.js";
|
|
|
6
6
|
import { servedVersionOf } from "../template-draft.js";
|
|
7
7
|
import { buildOverrideDiff } from "./override-diff.js";
|
|
8
8
|
import { kindSchema, localeSchema, templateSlugSchema } from "./shared.js";
|
|
9
|
-
|
|
10
|
-
const tenantIdsSchema = z.array(z.string());
|
|
9
|
+
import { loadEnabledTenantIds, pickBestRowPerTenant, TENANT_OVERRIDES_REASON, } from "./tenant-override-selection.js";
|
|
11
10
|
const directorySchema = z.object({
|
|
12
11
|
rows: z.array(z.object({ id: z.string(), label: z.string() })),
|
|
13
12
|
});
|
|
14
13
|
function labelsOf(raw) {
|
|
15
14
|
return new Map(directorySchema.parse(raw).rows.map((row) => [row.id, row.label]));
|
|
16
15
|
}
|
|
17
|
-
// One row per tenant: its active override wins over a draft, then the newest version.
|
|
18
|
-
function isBetterRow(candidate, current) {
|
|
19
|
-
if ((candidate.status === "active") !== (current.status === "active")) {
|
|
20
|
-
return candidate.status === "active";
|
|
21
|
-
}
|
|
22
|
-
return candidate.version > current.version;
|
|
23
|
-
}
|
|
24
16
|
function statusOf(row) {
|
|
25
17
|
return row.status === "active" ? "active" : "draft";
|
|
26
18
|
}
|
|
27
|
-
function isCountedTenantRow(row, enabledTenantIds) {
|
|
28
|
-
if (row.tenantId === SYSTEM_TENANT_ID || row.status === "archived")
|
|
29
|
-
return false;
|
|
30
|
-
return enabledTenantIds === null || enabledTenantIds.has(row.tenantId);
|
|
31
|
-
}
|
|
32
|
-
function pickBestRowPerTenant(found, enabledTenantIds) {
|
|
33
|
-
const bestByTenant = new Map();
|
|
34
|
-
for (const row of found) {
|
|
35
|
-
if (!isCountedTenantRow(row, enabledTenantIds))
|
|
36
|
-
continue;
|
|
37
|
-
const current = bestByTenant.get(row.tenantId);
|
|
38
|
-
if (current === undefined || isBetterRow(row, current))
|
|
39
|
-
bestByTenant.set(row.tenantId, row);
|
|
40
|
-
}
|
|
41
|
-
return bestByTenant;
|
|
42
|
-
}
|
|
43
19
|
function toOverrideRow(row, tenantLabels, userLabels) {
|
|
44
20
|
const changedById = row.modifiedById ?? row.insertedById;
|
|
45
21
|
return {
|
|
@@ -74,10 +50,8 @@ export const tenantOverridesQuery = defineQueryHandler({
|
|
|
74
50
|
scope: "tenant",
|
|
75
51
|
...(locale === undefined ? {} : { locale }),
|
|
76
52
|
});
|
|
77
|
-
const
|
|
78
|
-
const
|
|
79
|
-
? new Set(tenantIdsSchema.parse(await ctx.query(TenantQueries.activeTenantIds, {})))
|
|
80
|
-
: null;
|
|
53
|
+
const enabledTenantIds = await loadEnabledTenantIds(ctx);
|
|
54
|
+
const hasTenantFeature = enabledTenantIds !== null;
|
|
81
55
|
const bestByTenant = pickBestRowPerTenant(found, enabledTenantIds);
|
|
82
56
|
const best = [...bestByTenant.values()];
|
|
83
57
|
const tenantLabels = hasTenantFeature
|