@cosmicdrift/kumiko-bundled-features 0.398.0 → 0.400.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/auth-mfa/schema/user-mfa.d.ts +4 -4
- package/dist/billing-foundation/consumer-protection/consent-text.js +4 -4
- package/dist/billing-foundation/entities.d.ts +2 -2
- package/dist/billing-foundation/projection.d.ts +2 -2
- package/dist/cap-counter/entity.d.ts +3 -3
- package/dist/channel-in-app/constants.js +4 -9
- package/dist/channel-in-app/feature.js +1 -1
- package/dist/channel-in-app/handlers/inbox.query.d.ts +4 -1
- package/dist/channel-in-app/handlers/inbox.query.js +23 -8
- package/dist/channel-in-app/handlers/unread-count.query.js +2 -9
- package/dist/channel-in-app/in-app-channel.js +23 -11
- package/dist/channel-in-app/in-app-target.d.ts +5 -0
- package/dist/channel-in-app/in-app-target.js +34 -0
- package/dist/channel-in-app/index.d.ts +1 -0
- package/dist/channel-in-app/index.js +1 -0
- package/dist/compliance-profiles/schema/profile-selection.d.ts +2 -2
- package/dist/custom-fields/entity.d.ts +23 -3
- package/dist/delivery/tables.d.ts +11 -1
- package/dist/documents/aggregate-id.d.ts +2 -0
- package/dist/documents/aggregate-id.js +12 -0
- package/dist/documents/capture.d.ts +14 -0
- package/dist/documents/capture.js +65 -0
- package/dist/documents/constants.d.ts +23 -0
- package/dist/documents/constants.js +21 -0
- package/dist/documents/ensure-link.d.ts +17 -0
- package/dist/documents/ensure-link.js +20 -0
- package/dist/documents/entity.d.ts +76 -0
- package/dist/documents/entity.js +84 -0
- package/dist/documents/executor.d.ts +6 -0
- package/dist/documents/executor.js +8 -0
- package/dist/documents/expiry.d.ts +9 -0
- package/dist/documents/expiry.js +20 -0
- package/dist/documents/feature.d.ts +33 -0
- package/dist/documents/feature.js +111 -0
- package/dist/documents/handlers/import.write.d.ts +23 -0
- package/dist/documents/handlers/import.write.js +104 -0
- package/dist/documents/handlers/link.write.d.ts +18 -0
- package/dist/documents/handlers/link.write.js +61 -0
- package/dist/documents/i18n.d.ts +6 -0
- package/dist/documents/i18n.js +33 -0
- package/dist/documents/index.d.ts +7 -0
- package/dist/documents/index.js +6 -0
- package/dist/documents/pending-file-guard.d.ts +2 -0
- package/dist/documents/pending-file-guard.js +29 -0
- package/dist/documents/queries/expiry-overview.query.d.ts +10 -0
- package/dist/documents/queries/expiry-overview.query.js +41 -0
- package/dist/documents/queries/for-record.query.d.ts +90 -0
- package/dist/documents/queries/for-record.query.js +54 -0
- package/dist/documents/queries/list-page.d.ts +19 -0
- package/dist/documents/queries/list-page.js +36 -0
- package/dist/documents/queries/overview.query.d.ts +32 -0
- package/dist/documents/queries/overview.query.js +41 -0
- package/dist/documents/schemas.d.ts +93 -0
- package/dist/documents/schemas.js +51 -0
- package/dist/documents/screens.d.ts +3 -0
- package/dist/documents/screens.js +76 -0
- package/dist/documents/tab.d.ts +4 -0
- package/dist/documents/tab.js +42 -0
- package/dist/documents/target-write-check.d.ts +3 -0
- package/dist/documents/target-write-check.js +25 -0
- package/dist/documents/write-guard.d.ts +6 -0
- package/dist/documents/write-guard.js +46 -0
- package/dist/inbound-mail-foundation/entities.d.ts +17 -7
- package/dist/inbound-mail-foundation/ingest-failures.d.ts +3 -3
- package/dist/inbound-mail-foundation/projection.d.ts +15 -5
- package/dist/inbound-mail-foundation/quarantine-entities.d.ts +3 -3
- package/dist/jobs/job-run-table.d.ts +4 -4
- package/dist/ledger/entity.d.ts +8 -8
- package/dist/managed-pages/table.d.ts +24 -4
- package/dist/metrics/compute.d.ts +3 -0
- package/dist/metrics/compute.js +10 -3
- package/dist/metrics/feature.js +6 -2
- package/dist/metrics/types.d.ts +5 -1
- package/dist/metrics/validate.js +3 -0
- package/dist/personal-access-tokens/schema/api-token.d.ts +3 -3
- package/dist/readiness/schema/probe-run.d.ts +60 -20
- package/dist/secrets/table.d.ts +1 -1
- package/dist/sessions/schema/user-session.d.ts +4 -4
- package/dist/shared/purge-row-file-refs.js +5 -4
- package/dist/template-resolver/table.d.ts +12 -12
- package/dist/template-resolver/user-content-table.d.ts +4 -4
- package/dist/tenant/invitation-table.d.ts +6 -6
- package/dist/tenant/schema/tenant.d.ts +32 -12
- package/dist/tier-engine/entity.d.ts +1 -1
- package/dist/tier-engine/tier-resolver.d.ts +1 -1
- package/dist/user/schema/user.d.ts +28 -8
- package/dist/user-data-rights/schema/download-attempt.d.ts +2 -2
- package/dist/user-data-rights/schema/download-token.d.ts +8 -8
- package/dist/user-data-rights/schema/export-job.d.ts +12 -12
- package/dist/waitlist/entity.d.ts +8 -8
- package/package.json +19 -11
- package/src/billing-foundation/changes.json +6 -0
- package/src/channel-in-app/changes.json +6 -0
- package/src/metrics/changes.json +6 -0
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { createEntityExecutor, hasAccess, toKebab, userCanWriteFieldRow, } from "@cosmicdrift/kumiko-framework/engine";
|
|
2
|
+
import { parentRowIsVisible } from "../shared/index.js";
|
|
3
|
+
function updateHandlerAccessOf(registry, entityType) {
|
|
4
|
+
const suffix = `:write:${toKebab(entityType)}:update`;
|
|
5
|
+
for (const [qn, handler] of registry.getAllWriteHandlers()) {
|
|
6
|
+
if (qn.endsWith(suffix) && registry.getHandlerEntity(qn) === entityType)
|
|
7
|
+
return handler.access;
|
|
8
|
+
}
|
|
9
|
+
return undefined;
|
|
10
|
+
}
|
|
11
|
+
// Linking a document changes what readers of the target see, so it needs what saving the target
|
|
12
|
+
// needs: read visibility, the entity's update handler and its row-level write rule. A target that
|
|
13
|
+
// is not visible answers like a missing one.
|
|
14
|
+
export async function canWriteLinkTarget(registry, entityType, entityId, user, db) {
|
|
15
|
+
if (!(await parentRowIsVisible(registry, entityType, entityId, user, db)))
|
|
16
|
+
return false;
|
|
17
|
+
const entity = registry.getEntity(entityType);
|
|
18
|
+
if (entity === undefined)
|
|
19
|
+
return false;
|
|
20
|
+
const updateAccess = updateHandlerAccessOf(registry, entityType);
|
|
21
|
+
if (updateAccess === undefined || !hasAccess(user, updateAccess))
|
|
22
|
+
return false;
|
|
23
|
+
const row = await createEntityExecutor(entityType, entity).executor.detail({ id: entityId }, user, db);
|
|
24
|
+
return row !== null && userCanWriteFieldRow(user, entity.access?.write, row, row);
|
|
25
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { type DeleteContext, type HandlerContext, type PreSaveHookFn } from "@cosmicdrift/kumiko-framework/engine";
|
|
2
|
+
import type { DocumentExecutors } from "./executor.ts";
|
|
3
|
+
export declare function createDocumentWriteGuard(executors: DocumentExecutors, adminRoles: readonly string[]): {
|
|
4
|
+
beforeUpdate: PreSaveHookFn;
|
|
5
|
+
beforeDelete: (payload: DeleteContext, ctx: HandlerContext) => Promise<void>;
|
|
6
|
+
};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { SYSTEM_ROLE, } from "@cosmicdrift/kumiko-framework/engine";
|
|
2
|
+
import { NotFoundError } from "@cosmicdrift/kumiko-framework/errors";
|
|
3
|
+
import { DOCUMENT_ENTITY } from "./constants.js";
|
|
4
|
+
import { canWriteLinkTarget } from "./target-write-check.js";
|
|
5
|
+
const LINK_PAGE_SIZE = 200;
|
|
6
|
+
// Editing or deleting a document changes what every reader of its hosts sees, so it needs what
|
|
7
|
+
// unlink needs: write access to at least one host it is linked to. Admin roles see unlinked
|
|
8
|
+
// documents too and must be able to clean them up, and SYSTEM jobs (import, capture) write on
|
|
9
|
+
// their own authority. A refused caller gets "not found", like a document they cannot read.
|
|
10
|
+
export function createDocumentWriteGuard(executors, adminRoles) {
|
|
11
|
+
async function assertMayWrite(documentId, ctx) {
|
|
12
|
+
const { user } = ctx;
|
|
13
|
+
if (user.roles.includes(SYSTEM_ROLE) || user.roles.some((role) => adminRoles.includes(role))) {
|
|
14
|
+
// skip: exempt roles write on their own authority
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
let offset = 0;
|
|
18
|
+
for (;;) {
|
|
19
|
+
const page = await executors.link.list({
|
|
20
|
+
filters: [{ field: "documentId", op: "eq", value: documentId }],
|
|
21
|
+
limit: LINK_PAGE_SIZE,
|
|
22
|
+
offset,
|
|
23
|
+
}, user, ctx.db);
|
|
24
|
+
for (const row of page.rows) {
|
|
25
|
+
const entityType = String(row["entityType"]);
|
|
26
|
+
const entityId = String(row["entityId"]);
|
|
27
|
+
// skip: one writable host is enough
|
|
28
|
+
if (await canWriteLinkTarget(ctx.registry, entityType, entityId, user, ctx.db))
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
if (page.rows.length < LINK_PAGE_SIZE)
|
|
32
|
+
break;
|
|
33
|
+
offset += LINK_PAGE_SIZE;
|
|
34
|
+
}
|
|
35
|
+
throw new NotFoundError(DOCUMENT_ENTITY, documentId);
|
|
36
|
+
}
|
|
37
|
+
const beforeUpdate = async (changes, ctx) => {
|
|
38
|
+
if (!ctx.isNew)
|
|
39
|
+
await assertMayWrite(String(ctx.previous["id"]), ctx);
|
|
40
|
+
return changes;
|
|
41
|
+
};
|
|
42
|
+
const beforeDelete = async (payload, ctx) => {
|
|
43
|
+
await assertMayWrite(String(payload.id), ctx);
|
|
44
|
+
};
|
|
45
|
+
return { beforeUpdate, beforeDelete };
|
|
46
|
+
}
|
|
@@ -41,7 +41,7 @@ export declare const mailAccountEntity: import("@cosmicdrift/kumiko-types/fields
|
|
|
41
41
|
readonly min?: string;
|
|
42
42
|
readonly max?: string;
|
|
43
43
|
readonly locale?: string;
|
|
44
|
-
} & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
|
|
44
|
+
} & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & import("@cosmicdrift/kumiko-types/fields").FieldStateConditions & {
|
|
45
45
|
required: true;
|
|
46
46
|
};
|
|
47
47
|
}, "tenant">;
|
|
@@ -89,7 +89,7 @@ export declare const inboundMessageEntity: import("@cosmicdrift/kumiko-types/fie
|
|
|
89
89
|
readonly min?: string;
|
|
90
90
|
readonly max?: string;
|
|
91
91
|
readonly locale?: string;
|
|
92
|
-
} & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
|
|
92
|
+
} & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & import("@cosmicdrift/kumiko-types/fields").FieldStateConditions & {
|
|
93
93
|
required: true;
|
|
94
94
|
};
|
|
95
95
|
readonly bodyRef: import("@cosmicdrift/kumiko-types/fields").EntityTextFieldDef & {
|
|
@@ -101,7 +101,17 @@ export declare const inboundMessageEntity: import("@cosmicdrift/kumiko-types/fie
|
|
|
101
101
|
readonly bodyText: import("@cosmicdrift/kumiko-types/fields").EntityLongTextFieldDef & {
|
|
102
102
|
required: false;
|
|
103
103
|
};
|
|
104
|
-
readonly bodyTextTruncated:
|
|
104
|
+
readonly bodyTextTruncated: {
|
|
105
|
+
readonly type: "boolean";
|
|
106
|
+
readonly description?: string;
|
|
107
|
+
readonly required?: boolean;
|
|
108
|
+
readonly sortable?: boolean;
|
|
109
|
+
readonly sortNulls?: import("@cosmicdrift/kumiko-types/fields").SortNulls;
|
|
110
|
+
readonly filterable?: boolean;
|
|
111
|
+
readonly sensitive?: boolean;
|
|
112
|
+
readonly default?: boolean;
|
|
113
|
+
readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
|
|
114
|
+
} & import("@cosmicdrift/kumiko-types/fields").FieldStateConditions & {
|
|
105
115
|
required: false;
|
|
106
116
|
default: boolean;
|
|
107
117
|
};
|
|
@@ -141,7 +151,7 @@ export declare const mailThreadEntity: import("@cosmicdrift/kumiko-types/fields"
|
|
|
141
151
|
readonly min?: string;
|
|
142
152
|
readonly max?: string;
|
|
143
153
|
readonly locale?: string;
|
|
144
|
-
} & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
|
|
154
|
+
} & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & import("@cosmicdrift/kumiko-types/fields").FieldStateConditions & {
|
|
145
155
|
required: true;
|
|
146
156
|
};
|
|
147
157
|
readonly messageCount: {
|
|
@@ -161,7 +171,7 @@ export declare const mailThreadEntity: import("@cosmicdrift/kumiko-types/fields"
|
|
|
161
171
|
readonly field: string;
|
|
162
172
|
};
|
|
163
173
|
readonly grouping?: boolean;
|
|
164
|
-
} & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
|
|
174
|
+
} & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & import("@cosmicdrift/kumiko-types/fields").FieldStateConditions & {
|
|
165
175
|
required: true;
|
|
166
176
|
};
|
|
167
177
|
}, "tenant">;
|
|
@@ -195,7 +205,7 @@ export declare const syncCursorEntity: import("@cosmicdrift/kumiko-types/fields"
|
|
|
195
205
|
readonly min?: string;
|
|
196
206
|
readonly max?: string;
|
|
197
207
|
readonly locale?: string;
|
|
198
|
-
} & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
|
|
208
|
+
} & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & import("@cosmicdrift/kumiko-types/fields").FieldStateConditions & {
|
|
199
209
|
required: true;
|
|
200
210
|
};
|
|
201
211
|
}, "tenant">;
|
|
@@ -223,7 +233,7 @@ export declare const seenMessageEntity: import("@cosmicdrift/kumiko-types/fields
|
|
|
223
233
|
readonly min?: string;
|
|
224
234
|
readonly max?: string;
|
|
225
235
|
readonly locale?: string;
|
|
226
|
-
} & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
|
|
236
|
+
} & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & import("@cosmicdrift/kumiko-types/fields").FieldStateConditions & {
|
|
227
237
|
required: true;
|
|
228
238
|
};
|
|
229
239
|
}, "tenant">;
|
|
@@ -25,7 +25,7 @@ export declare const ingestFailureEntity: import("@cosmicdrift/kumiko-types/fiel
|
|
|
25
25
|
readonly field: string;
|
|
26
26
|
};
|
|
27
27
|
readonly grouping?: boolean;
|
|
28
|
-
} & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
|
|
28
|
+
} & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & import("@cosmicdrift/kumiko-types/fields").FieldStateConditions & {
|
|
29
29
|
required: true;
|
|
30
30
|
};
|
|
31
31
|
readonly lastCountedBucket: {
|
|
@@ -45,7 +45,7 @@ export declare const ingestFailureEntity: import("@cosmicdrift/kumiko-types/fiel
|
|
|
45
45
|
readonly field: string;
|
|
46
46
|
};
|
|
47
47
|
readonly grouping?: boolean;
|
|
48
|
-
} & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
|
|
48
|
+
} & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & import("@cosmicdrift/kumiko-types/fields").FieldStateConditions & {
|
|
49
49
|
required: true;
|
|
50
50
|
};
|
|
51
51
|
readonly lastFailedAt: {
|
|
@@ -61,7 +61,7 @@ export declare const ingestFailureEntity: import("@cosmicdrift/kumiko-types/fiel
|
|
|
61
61
|
readonly min?: string;
|
|
62
62
|
readonly max?: string;
|
|
63
63
|
readonly locale?: string;
|
|
64
|
-
} & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
|
|
64
|
+
} & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & import("@cosmicdrift/kumiko-types/fields").FieldStateConditions & {
|
|
65
65
|
required: true;
|
|
66
66
|
};
|
|
67
67
|
}, "tenant">;
|
|
@@ -39,7 +39,7 @@ export declare const mailAccountsProjectionTable: import("@cosmicdrift/kumiko-fr
|
|
|
39
39
|
readonly min?: string;
|
|
40
40
|
readonly max?: string;
|
|
41
41
|
readonly locale?: string;
|
|
42
|
-
} & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
|
|
42
|
+
} & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & import("@cosmicdrift/kumiko-types/fields").FieldStateConditions & {
|
|
43
43
|
required: true;
|
|
44
44
|
};
|
|
45
45
|
}, "tenant">>;
|
|
@@ -84,7 +84,7 @@ export declare const inboundMessagesProjectionTable: import("@cosmicdrift/kumiko
|
|
|
84
84
|
readonly min?: string;
|
|
85
85
|
readonly max?: string;
|
|
86
86
|
readonly locale?: string;
|
|
87
|
-
} & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
|
|
87
|
+
} & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & import("@cosmicdrift/kumiko-types/fields").FieldStateConditions & {
|
|
88
88
|
required: true;
|
|
89
89
|
};
|
|
90
90
|
readonly bodyRef: import("@cosmicdrift/kumiko-types/fields").EntityTextFieldDef & {
|
|
@@ -96,7 +96,17 @@ export declare const inboundMessagesProjectionTable: import("@cosmicdrift/kumiko
|
|
|
96
96
|
readonly bodyText: import("@cosmicdrift/kumiko-types/fields").EntityLongTextFieldDef & {
|
|
97
97
|
required: false;
|
|
98
98
|
};
|
|
99
|
-
readonly bodyTextTruncated:
|
|
99
|
+
readonly bodyTextTruncated: {
|
|
100
|
+
readonly type: "boolean";
|
|
101
|
+
readonly description?: string;
|
|
102
|
+
readonly required?: boolean;
|
|
103
|
+
readonly sortable?: boolean;
|
|
104
|
+
readonly sortNulls?: import("@cosmicdrift/kumiko-types/fields").SortNulls;
|
|
105
|
+
readonly filterable?: boolean;
|
|
106
|
+
readonly sensitive?: boolean;
|
|
107
|
+
readonly default?: boolean;
|
|
108
|
+
readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
|
|
109
|
+
} & import("@cosmicdrift/kumiko-types/fields").FieldStateConditions & {
|
|
100
110
|
required: false;
|
|
101
111
|
default: boolean;
|
|
102
112
|
};
|
|
@@ -134,7 +144,7 @@ export declare const mailThreadsProjectionTable: import("@cosmicdrift/kumiko-fra
|
|
|
134
144
|
readonly min?: string;
|
|
135
145
|
readonly max?: string;
|
|
136
146
|
readonly locale?: string;
|
|
137
|
-
} & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
|
|
147
|
+
} & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & import("@cosmicdrift/kumiko-types/fields").FieldStateConditions & {
|
|
138
148
|
required: true;
|
|
139
149
|
};
|
|
140
150
|
readonly messageCount: {
|
|
@@ -154,7 +164,7 @@ export declare const mailThreadsProjectionTable: import("@cosmicdrift/kumiko-fra
|
|
|
154
164
|
readonly field: string;
|
|
155
165
|
};
|
|
156
166
|
readonly grouping?: boolean;
|
|
157
|
-
} & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
|
|
167
|
+
} & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & import("@cosmicdrift/kumiko-types/fields").FieldStateConditions & {
|
|
158
168
|
required: true;
|
|
159
169
|
};
|
|
160
170
|
}, "tenant">>;
|
|
@@ -25,7 +25,7 @@ export declare const quarantineEntity: import("@cosmicdrift/kumiko-types/fields"
|
|
|
25
25
|
readonly min?: string;
|
|
26
26
|
readonly max?: string;
|
|
27
27
|
readonly locale?: string;
|
|
28
|
-
} & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
|
|
28
|
+
} & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & import("@cosmicdrift/kumiko-types/fields").FieldStateConditions & {
|
|
29
29
|
required: true;
|
|
30
30
|
};
|
|
31
31
|
readonly acknowledgedAt: {
|
|
@@ -41,7 +41,7 @@ export declare const quarantineEntity: import("@cosmicdrift/kumiko-types/fields"
|
|
|
41
41
|
readonly min?: string;
|
|
42
42
|
readonly max?: string;
|
|
43
43
|
readonly locale?: string;
|
|
44
|
-
} & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
|
|
44
|
+
} & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & import("@cosmicdrift/kumiko-types/fields").FieldStateConditions & {
|
|
45
45
|
required: false;
|
|
46
46
|
};
|
|
47
47
|
readonly retryRequestedAt: {
|
|
@@ -57,7 +57,7 @@ export declare const quarantineEntity: import("@cosmicdrift/kumiko-types/fields"
|
|
|
57
57
|
readonly min?: string;
|
|
58
58
|
readonly max?: string;
|
|
59
59
|
readonly locale?: string;
|
|
60
|
-
} & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
|
|
60
|
+
} & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & import("@cosmicdrift/kumiko-types/fields").FieldStateConditions & {
|
|
61
61
|
required: false;
|
|
62
62
|
};
|
|
63
63
|
}, "tenant">;
|
|
@@ -34,7 +34,7 @@ export declare const jobRunEntity: import("@cosmicdrift/kumiko-types/fields").En
|
|
|
34
34
|
readonly field: string;
|
|
35
35
|
};
|
|
36
36
|
readonly grouping?: boolean;
|
|
37
|
-
} & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
|
|
37
|
+
} & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & import("@cosmicdrift/kumiko-types/fields").FieldStateConditions & {
|
|
38
38
|
required: true;
|
|
39
39
|
};
|
|
40
40
|
readonly startedAt: {
|
|
@@ -50,7 +50,7 @@ export declare const jobRunEntity: import("@cosmicdrift/kumiko-types/fields").En
|
|
|
50
50
|
readonly min?: string;
|
|
51
51
|
readonly max?: string;
|
|
52
52
|
readonly locale?: string;
|
|
53
|
-
} & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
|
|
53
|
+
} & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & import("@cosmicdrift/kumiko-types/fields").FieldStateConditions & {
|
|
54
54
|
required: true;
|
|
55
55
|
};
|
|
56
56
|
readonly finishedAt: {
|
|
@@ -66,7 +66,7 @@ export declare const jobRunEntity: import("@cosmicdrift/kumiko-types/fields").En
|
|
|
66
66
|
readonly min?: string;
|
|
67
67
|
readonly max?: string;
|
|
68
68
|
readonly locale?: string;
|
|
69
|
-
} & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
|
|
69
|
+
} & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & import("@cosmicdrift/kumiko-types/fields").FieldStateConditions & {
|
|
70
70
|
required: false;
|
|
71
71
|
};
|
|
72
72
|
readonly duration: {
|
|
@@ -86,7 +86,7 @@ export declare const jobRunEntity: import("@cosmicdrift/kumiko-types/fields").En
|
|
|
86
86
|
readonly field: string;
|
|
87
87
|
};
|
|
88
88
|
readonly grouping?: boolean;
|
|
89
|
-
} & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
|
|
89
|
+
} & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & import("@cosmicdrift/kumiko-types/fields").FieldStateConditions & {
|
|
90
90
|
required: false;
|
|
91
91
|
};
|
|
92
92
|
readonly triggeredById: import("@cosmicdrift/kumiko-types/fields").EntityTextFieldDef & {
|
package/dist/ledger/entity.d.ts
CHANGED
|
@@ -25,7 +25,7 @@ export declare const accountEntity: import("@cosmicdrift/kumiko-types/fields").E
|
|
|
25
25
|
readonly optionsQueryPayload?: import("@cosmicdrift/kumiko-types/fields").OptionsQueryPayload;
|
|
26
26
|
readonly optionsAvailabilityQuery?: string;
|
|
27
27
|
readonly conditionalOptions?: readonly import("@cosmicdrift/kumiko-types/fields").SelectConditionalOptions<"asset" | "equity" | "expense" | "income" | "liability">[] | undefined;
|
|
28
|
-
} & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
|
|
28
|
+
} & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & import("@cosmicdrift/kumiko-types/fields").FieldStateConditions & {
|
|
29
29
|
required: true;
|
|
30
30
|
};
|
|
31
31
|
readonly code: import("@cosmicdrift/kumiko-types/fields").EntityTextFieldDef & {
|
|
@@ -48,7 +48,7 @@ export declare const transactionEntity: import("@cosmicdrift/kumiko-types/fields
|
|
|
48
48
|
readonly min?: string;
|
|
49
49
|
readonly max?: string;
|
|
50
50
|
readonly locale?: string;
|
|
51
|
-
} & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
|
|
51
|
+
} & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & import("@cosmicdrift/kumiko-types/fields").FieldStateConditions & {
|
|
52
52
|
required: true;
|
|
53
53
|
};
|
|
54
54
|
readonly description: import("@cosmicdrift/kumiko-types/fields").EntityTextFieldDef & {
|
|
@@ -77,7 +77,7 @@ export declare const transactionEntity: import("@cosmicdrift/kumiko-types/fields
|
|
|
77
77
|
readonly optionsQueryPayload?: import("@cosmicdrift/kumiko-types/fields").OptionsQueryPayload;
|
|
78
78
|
readonly optionsAvailabilityQuery?: string;
|
|
79
79
|
readonly conditionalOptions?: readonly import("@cosmicdrift/kumiko-types/fields").SelectConditionalOptions<"draft" | "posted">[] | undefined;
|
|
80
|
-
} & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
|
|
80
|
+
} & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & import("@cosmicdrift/kumiko-types/fields").FieldStateConditions & {
|
|
81
81
|
required: true;
|
|
82
82
|
};
|
|
83
83
|
readonly lines: {
|
|
@@ -93,7 +93,7 @@ export declare const transactionEntity: import("@cosmicdrift/kumiko-types/fields
|
|
|
93
93
|
readonly derived?: Readonly<Record<string, import("@cosmicdrift/kumiko-types/fields").EmbeddedDerivedCellDef>>;
|
|
94
94
|
readonly totals?: readonly import("@cosmicdrift/kumiko-types/fields").EmbeddedTotalDef[];
|
|
95
95
|
readonly totalsMatch?: Readonly<Record<string, string>>;
|
|
96
|
-
} & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
|
|
96
|
+
} & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & import("@cosmicdrift/kumiko-types/fields").FieldStateConditions & {
|
|
97
97
|
multiple: true;
|
|
98
98
|
};
|
|
99
99
|
readonly subjectType: import("@cosmicdrift/kumiko-types/fields").EntityTextFieldDef & {
|
|
@@ -119,7 +119,7 @@ export declare const scheduleEntity: import("@cosmicdrift/kumiko-types/fields").
|
|
|
119
119
|
readonly min?: string;
|
|
120
120
|
readonly max?: string;
|
|
121
121
|
readonly locale?: string;
|
|
122
|
-
} & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
|
|
122
|
+
} & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & import("@cosmicdrift/kumiko-types/fields").FieldStateConditions & {
|
|
123
123
|
required: true;
|
|
124
124
|
};
|
|
125
125
|
readonly endDate: {
|
|
@@ -134,7 +134,7 @@ export declare const scheduleEntity: import("@cosmicdrift/kumiko-types/fields").
|
|
|
134
134
|
readonly min?: string;
|
|
135
135
|
readonly max?: string;
|
|
136
136
|
readonly locale?: string;
|
|
137
|
-
} & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
|
|
137
|
+
} & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & import("@cosmicdrift/kumiko-types/fields").FieldStateConditions & {
|
|
138
138
|
required: false;
|
|
139
139
|
};
|
|
140
140
|
readonly interval: {
|
|
@@ -156,7 +156,7 @@ export declare const scheduleEntity: import("@cosmicdrift/kumiko-types/fields").
|
|
|
156
156
|
readonly optionsQueryPayload?: import("@cosmicdrift/kumiko-types/fields").OptionsQueryPayload;
|
|
157
157
|
readonly optionsAvailabilityQuery?: string;
|
|
158
158
|
readonly conditionalOptions?: readonly import("@cosmicdrift/kumiko-types/fields").SelectConditionalOptions<"monthly">[] | undefined;
|
|
159
|
-
} & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
|
|
159
|
+
} & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & import("@cosmicdrift/kumiko-types/fields").FieldStateConditions & {
|
|
160
160
|
required: true;
|
|
161
161
|
};
|
|
162
162
|
readonly amount: {
|
|
@@ -176,7 +176,7 @@ export declare const scheduleEntity: import("@cosmicdrift/kumiko-types/fields").
|
|
|
176
176
|
readonly field: string;
|
|
177
177
|
};
|
|
178
178
|
readonly grouping?: boolean;
|
|
179
|
-
} & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
|
|
179
|
+
} & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & import("@cosmicdrift/kumiko-types/fields").FieldStateConditions & {
|
|
180
180
|
required: true;
|
|
181
181
|
};
|
|
182
182
|
readonly debitAccountId: import("@cosmicdrift/kumiko-types/fields").EntityTextFieldDef & {
|
|
@@ -32,7 +32,7 @@ export declare const pageEntity: import("@cosmicdrift/kumiko-types/fields").Enti
|
|
|
32
32
|
readonly optionsQueryPayload?: import("@cosmicdrift/kumiko-types/fields").OptionsQueryPayload;
|
|
33
33
|
readonly optionsAvailabilityQuery?: string;
|
|
34
34
|
readonly conditionalOptions?: readonly import("@cosmicdrift/kumiko-types/fields").SelectConditionalOptions<"document" | "markdown">[] | undefined;
|
|
35
|
-
} & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
|
|
35
|
+
} & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & import("@cosmicdrift/kumiko-types/fields").FieldStateConditions & {
|
|
36
36
|
required: false;
|
|
37
37
|
};
|
|
38
38
|
readonly description: import("@cosmicdrift/kumiko-types/fields").EntityTextFieldDef & {
|
|
@@ -41,7 +41,17 @@ export declare const pageEntity: import("@cosmicdrift/kumiko-types/fields").Enti
|
|
|
41
41
|
readonly ogImage: import("@cosmicdrift/kumiko-types/fields").EntityTextFieldDef & {
|
|
42
42
|
required: false;
|
|
43
43
|
};
|
|
44
|
-
readonly published:
|
|
44
|
+
readonly published: {
|
|
45
|
+
readonly type: "boolean";
|
|
46
|
+
readonly description?: string;
|
|
47
|
+
readonly required?: boolean;
|
|
48
|
+
readonly sortable?: boolean;
|
|
49
|
+
readonly sortNulls?: import("@cosmicdrift/kumiko-types/fields").SortNulls;
|
|
50
|
+
readonly filterable?: boolean;
|
|
51
|
+
readonly sensitive?: boolean;
|
|
52
|
+
readonly default?: boolean;
|
|
53
|
+
readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
|
|
54
|
+
} & import("@cosmicdrift/kumiko-types/fields").FieldStateConditions & {
|
|
45
55
|
required: false;
|
|
46
56
|
default: boolean;
|
|
47
57
|
};
|
|
@@ -79,7 +89,7 @@ export declare const pagesTable: import("@cosmicdrift/kumiko-framework/db").Enti
|
|
|
79
89
|
readonly optionsQueryPayload?: import("@cosmicdrift/kumiko-types/fields").OptionsQueryPayload;
|
|
80
90
|
readonly optionsAvailabilityQuery?: string;
|
|
81
91
|
readonly conditionalOptions?: readonly import("@cosmicdrift/kumiko-types/fields").SelectConditionalOptions<"document" | "markdown">[] | undefined;
|
|
82
|
-
} & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
|
|
92
|
+
} & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & import("@cosmicdrift/kumiko-types/fields").FieldStateConditions & {
|
|
83
93
|
required: false;
|
|
84
94
|
};
|
|
85
95
|
readonly description: import("@cosmicdrift/kumiko-types/fields").EntityTextFieldDef & {
|
|
@@ -88,7 +98,17 @@ export declare const pagesTable: import("@cosmicdrift/kumiko-framework/db").Enti
|
|
|
88
98
|
readonly ogImage: import("@cosmicdrift/kumiko-types/fields").EntityTextFieldDef & {
|
|
89
99
|
required: false;
|
|
90
100
|
};
|
|
91
|
-
readonly published:
|
|
101
|
+
readonly published: {
|
|
102
|
+
readonly type: "boolean";
|
|
103
|
+
readonly description?: string;
|
|
104
|
+
readonly required?: boolean;
|
|
105
|
+
readonly sortable?: boolean;
|
|
106
|
+
readonly sortNulls?: import("@cosmicdrift/kumiko-types/fields").SortNulls;
|
|
107
|
+
readonly filterable?: boolean;
|
|
108
|
+
readonly sensitive?: boolean;
|
|
109
|
+
readonly default?: boolean;
|
|
110
|
+
readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
|
|
111
|
+
} & import("@cosmicdrift/kumiko-types/fields").FieldStateConditions & {
|
|
92
112
|
required: false;
|
|
93
113
|
default: boolean;
|
|
94
114
|
};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { WhereObject } from "@cosmicdrift/kumiko-framework/db";
|
|
2
|
+
import type { AggregateKey } from "@cosmicdrift/kumiko-types/aggregate-types";
|
|
2
3
|
import type { Temporal } from "@cosmicdrift/kumiko-types/temporal";
|
|
3
4
|
import type { TenantDb } from "@cosmicdrift/kumiko-types/tenant-db-types";
|
|
4
5
|
import type { MetricDefinition, MetricResult } from "./types.ts";
|
|
@@ -11,5 +12,7 @@ export type MetricRunParams = {
|
|
|
11
12
|
readonly filterWhere: WhereObject;
|
|
12
13
|
/** Extra filter that narrows the source to the caller's scope. */
|
|
13
14
|
readonly scopeWhere: WhereObject;
|
|
15
|
+
/** Names for referenced-row group keys; absent where the caller cannot resolve them (system scope). */
|
|
16
|
+
readonly referenceLabels?: (keys: readonly AggregateKey[]) => Promise<ReadonlyMap<string, string>>;
|
|
14
17
|
};
|
|
15
18
|
export declare function runMetric(db: TenantDb, metric: MetricDefinition, params: MetricRunParams): Promise<MetricResult>;
|
package/dist/metrics/compute.js
CHANGED
|
@@ -75,7 +75,7 @@ function groupSegments(stackRows, labelOf) {
|
|
|
75
75
|
}
|
|
76
76
|
return segmentsByGroup;
|
|
77
77
|
}
|
|
78
|
-
function buildRows(metric, aggregates, total, labelOf) {
|
|
78
|
+
function buildRows(metric, aggregates, total, labelOf, groupLabelOf) {
|
|
79
79
|
const segmentsByGroup = groupSegments(aggregates.stacks, labelOf);
|
|
80
80
|
return aggregates.groups.map((row) => {
|
|
81
81
|
const key = keyText(row.keys[0]);
|
|
@@ -83,7 +83,7 @@ function buildRows(metric, aggregates, total, labelOf) {
|
|
|
83
83
|
return {
|
|
84
84
|
id: key,
|
|
85
85
|
key,
|
|
86
|
-
label:
|
|
86
|
+
label: groupLabelOf(key),
|
|
87
87
|
value: rowValue,
|
|
88
88
|
fraction: fractionOf(rowValue, total),
|
|
89
89
|
...(metric.stackBy !== undefined && { segments: segmentsByGroup.get(key) ?? [] }),
|
|
@@ -104,6 +104,11 @@ function buildSeries(rows, seriesRows, fillBuckets) {
|
|
|
104
104
|
points: fillBuckets(bucketsByGroup.get(row.key) ?? new Map()),
|
|
105
105
|
}));
|
|
106
106
|
}
|
|
107
|
+
async function resolveReferencedGroupLabels(metric, params, groups) {
|
|
108
|
+
if (params.referenceLabels === undefined || metric.groupBy === undefined)
|
|
109
|
+
return new Map();
|
|
110
|
+
return params.referenceLabels(groups.map((row) => row.keys[0] ?? null));
|
|
111
|
+
}
|
|
107
112
|
export async function runMetric(db, metric, params) {
|
|
108
113
|
const range = metric.window ?? params.range;
|
|
109
114
|
const bucket = resolveBucket(metric, range);
|
|
@@ -129,7 +134,9 @@ export async function runMetric(db, metric, params) {
|
|
|
129
134
|
row.value,
|
|
130
135
|
])))
|
|
131
136
|
: [];
|
|
132
|
-
const
|
|
137
|
+
const referencedLabels = await resolveReferencedGroupLabels(metric, params, aggregates.groups);
|
|
138
|
+
const groupLabelOf = (key) => metric.groupLabels?.[key] ?? referencedLabels.get(key) ?? key;
|
|
139
|
+
const rows = buildRows(metric, aggregates, value, labelOf, groupLabelOf);
|
|
133
140
|
return {
|
|
134
141
|
value,
|
|
135
142
|
previousValue,
|
package/dist/metrics/feature.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { extractTableInfo } from "@cosmicdrift/kumiko-framework/db";
|
|
2
|
+
import { access, defineFeature, defineQueryHandler, referenceGroupLabels, } from "@cosmicdrift/kumiko-framework/engine";
|
|
2
3
|
import { InternalError, ValidationError } from "@cosmicdrift/kumiko-framework/errors";
|
|
3
4
|
import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
|
|
4
5
|
import * as z from "zod";
|
|
@@ -45,7 +46,7 @@ function createTenantMetricQuery(metric, now) {
|
|
|
45
46
|
name: metric.id,
|
|
46
47
|
description: metric.description,
|
|
47
48
|
schema: tenantPayloadSchema(metric),
|
|
48
|
-
access: { roles: access.admin },
|
|
49
|
+
access: { roles: metric.access?.roles ?? access.admin },
|
|
49
50
|
handler: async (query, ctx) => runMetric(ctx.db, metric, {
|
|
50
51
|
range: query.payload.range,
|
|
51
52
|
timeZone: query.payload.timeZone,
|
|
@@ -53,6 +54,9 @@ function createTenantMetricQuery(metric, now) {
|
|
|
53
54
|
filterWhere: await resolveMetricFilterWhere(metric, query.payload, ctx),
|
|
54
55
|
// ctx.db already reads own-tenant + SYSTEM reference rows; this narrows to own-tenant only.
|
|
55
56
|
scopeWhere: { tenantId: query.user.tenantId },
|
|
57
|
+
referenceLabels: (keys) => metric.groupBy === undefined
|
|
58
|
+
? Promise.resolve(new Map())
|
|
59
|
+
: referenceGroupLabels(ctx, query.user, extractTableInfo(metric.source).name, metric.groupBy, keys, ctx.db),
|
|
56
60
|
}),
|
|
57
61
|
});
|
|
58
62
|
}
|
package/dist/metrics/types.d.ts
CHANGED
|
@@ -33,8 +33,12 @@ export type MetricDefinition = {
|
|
|
33
33
|
readonly groupBy?: string;
|
|
34
34
|
/** Second dimension, becomes `rows[].segments`; only without bucket. */
|
|
35
35
|
readonly stackBy?: string;
|
|
36
|
-
/** Key value → i18n key, applied to groupBy and stackBy keys. */
|
|
36
|
+
/** Key value → i18n key, applied to groupBy and stackBy keys. Without an entry, a groupBy that is a single reference of an entity-table source is named after the referenced row (tenant scope, through the caller's read access); otherwise the key stays. */
|
|
37
37
|
readonly groupLabels?: Readonly<Record<string, string>>;
|
|
38
|
+
/** Roles that may read the tenant query; default is the admin roles. Ignored for scope "system", which stays SystemAdmin only. */
|
|
39
|
+
readonly access?: {
|
|
40
|
+
readonly roles: readonly string[];
|
|
41
|
+
};
|
|
38
42
|
readonly scopes: readonly MetricScope[];
|
|
39
43
|
};
|
|
40
44
|
export declare function defineMetric(definition: MetricDefinition): MetricDefinition;
|
package/dist/metrics/validate.js
CHANGED
|
@@ -25,6 +25,9 @@ function validateMetric(metric) {
|
|
|
25
25
|
fail("has an id that is not kebab-case");
|
|
26
26
|
if (metric.scopes.length === 0)
|
|
27
27
|
fail("declares no scopes");
|
|
28
|
+
if (metric.access !== undefined && metric.access.roles.length === 0) {
|
|
29
|
+
fail("declares access with an empty roles list");
|
|
30
|
+
}
|
|
28
31
|
const info = extractTableInfo(metric.source);
|
|
29
32
|
validateColumns(metric, info, fail);
|
|
30
33
|
validateFilters(metric, info, fail);
|
|
@@ -30,7 +30,7 @@ export declare const apiTokenEntity: import("@cosmicdrift/kumiko-types/fields").
|
|
|
30
30
|
readonly min?: string;
|
|
31
31
|
readonly max?: string;
|
|
32
32
|
readonly locale?: string;
|
|
33
|
-
} & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
|
|
33
|
+
} & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & import("@cosmicdrift/kumiko-types/fields").FieldStateConditions & {
|
|
34
34
|
required: true;
|
|
35
35
|
};
|
|
36
36
|
readonly expiresAt: {
|
|
@@ -46,7 +46,7 @@ export declare const apiTokenEntity: import("@cosmicdrift/kumiko-types/fields").
|
|
|
46
46
|
readonly min?: string;
|
|
47
47
|
readonly max?: string;
|
|
48
48
|
readonly locale?: string;
|
|
49
|
-
} & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
|
|
49
|
+
} & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & import("@cosmicdrift/kumiko-types/fields").FieldStateConditions & {
|
|
50
50
|
required: false;
|
|
51
51
|
};
|
|
52
52
|
readonly revokedAt: {
|
|
@@ -62,7 +62,7 @@ export declare const apiTokenEntity: import("@cosmicdrift/kumiko-types/fields").
|
|
|
62
62
|
readonly min?: string;
|
|
63
63
|
readonly max?: string;
|
|
64
64
|
readonly locale?: string;
|
|
65
|
-
} & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
|
|
65
|
+
} & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & import("@cosmicdrift/kumiko-types/fields").FieldStateConditions & {
|
|
66
66
|
required: false;
|
|
67
67
|
};
|
|
68
68
|
}, "tenant">;
|