@cosmicdrift/kumiko-samples 0.371.1 → 0.372.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/package.json +1 -1
- package/packages/bundled-features/package.json +1 -1
- package/packages/bundled-features/src/auth-mfa/db/queries.ts +5 -7
- package/packages/bundled-features/src/cap-overview/web/cap-cards-panel.tsx +2 -1
- package/packages/bundled-features/src/config/web/index.ts +2 -0
- package/packages/bundled-features/src/crypto-shredding/changes.json +6 -0
- package/packages/bundled-features/src/crypto-shredding/handlers/forget-subject.write.ts +75 -34
- package/packages/bundled-features/src/crypto-shredding/resolve-file-bytes-deleter.ts +29 -0
- package/packages/bundled-features/src/data-retention/changes.json +6 -0
- package/packages/bundled-features/src/data-retention/index.ts +6 -0
- package/packages/bundled-features/src/data-retention/run-retention-cleanup.ts +37 -213
- package/packages/bundled-features/src/delivery/changes.json +13 -0
- package/packages/bundled-features/src/delivery/delivery-service.ts +7 -2
- package/packages/bundled-features/src/delivery/handlers/unsubscribe-page-chrome.query.ts +2 -2
- package/packages/bundled-features/src/delivery/index.ts +2 -0
- package/packages/bundled-features/src/delivery/jobs.ts +42 -23
- package/packages/bundled-features/src/delivery/recipient-address-seal.ts +72 -0
- package/packages/bundled-features/src/delivery/testing.ts +4 -1
- package/packages/bundled-features/src/delivery/unsubscribe.ts +23 -2
- package/packages/bundled-features/src/file-foundation/read-file-ref-content.ts +2 -2
- package/packages/bundled-features/src/files/changes.json +7 -0
- package/packages/bundled-features/src/personal-access-tokens/changes.json +12 -0
- package/packages/bundled-features/src/personal-access-tokens/screens.ts +15 -9
- package/packages/bundled-features/src/shared/index.ts +14 -0
- package/packages/bundled-features/src/shared/purge-files-reaching-subject.ts +206 -0
- package/packages/bundled-features/src/shared/purge-row-file-refs.ts +214 -0
- package/packages/bundled-features/src/template-resolver/changes.json +6 -0
- package/packages/bundled-features/src/template-resolver/template-preview.ts +24 -4
- package/packages/bundled-features/src/template-resolver/web/client-plugin.tsx +2 -1
- package/packages/bundled-features/src/template-resolver/web/document-editor-extensions.ts +1 -9
- package/packages/bundled-features/src/template-resolver/web/placeholder-line-editor.tsx +2 -2
- package/packages/bundled-features/src/user-data-rights/changes.json +6 -0
- package/packages/bundled-features/src/user-data-rights/run-forget-cleanup.ts +46 -1
- package/packages/bundled-features/src/user-data-rights/web/privacy-center-screen.tsx +2 -1
- package/samples/apps/styleguide/src/features/demo/feature.ts +1 -1
- package/samples/apps/ui-walkthrough/src/app/seed-rental.ts +5 -0
- package/samples/apps/ui-walkthrough/src/app/template-seed-data.ts +2 -0
- package/samples/apps/ui-walkthrough/src/features/billing/feature.ts +9 -0
- package/samples/apps/ui-walkthrough/src/features/billing/i18n.ts +6 -0
- package/samples/apps/ui-walkthrough/src/features/rental/entities.ts +1 -0
- package/samples/apps/ui-walkthrough/src/features/rental/feature.ts +27 -1
- package/samples/apps/ui-walkthrough/src/features/rental/i18n.ts +9 -0
- package/samples/apps/ui-walkthrough/src/features/rental/lease-support.ts +1 -0
- package/samples/apps/ui-walkthrough/src/features/rental/screens.ts +22 -1
- package/samples/apps/use-all-bundled/package.json +3 -1
- package/samples/recipes/custom-handlers/src/feature.ts +3 -5
- package/samples/recipes/embedded-entity-form/src/feature.ts +1 -1
- package/samples/recipes/pdf-audit-export/README.md +1 -1
- package/samples/recipes/user-data-rights/README.md +22 -0
- package/samples/recipes/user-data-rights/src/retained-docs.ts +35 -0
|
@@ -178,6 +178,7 @@ export const leaseDetailScreen: ProjectionDetailScreenDefinition = {
|
|
|
178
178
|
einheit: "rental:entity:lease:field:einheit",
|
|
179
179
|
liegenschaft: "rental:entity:lease:field:liegenschaft",
|
|
180
180
|
ende: "rental:entity:lease:field:ende",
|
|
181
|
+
vorgaenger: "rental:entity:lease:field:vorgaenger",
|
|
181
182
|
kontostand: "rental:entity:lease:field:kontostand",
|
|
182
183
|
notizen: "rental:entity:lease:field:notizen",
|
|
183
184
|
},
|
|
@@ -268,7 +269,13 @@ export const leaseDetailScreen: ProjectionDetailScreenDefinition = {
|
|
|
268
269
|
kind: "fields",
|
|
269
270
|
title: "rental.tab.contractData",
|
|
270
271
|
columns: 2,
|
|
271
|
-
fields: [
|
|
272
|
+
fields: [
|
|
273
|
+
"einheit",
|
|
274
|
+
"liegenschaft",
|
|
275
|
+
"beginn",
|
|
276
|
+
"ende",
|
|
277
|
+
{ field: "vorgaenger", refEntity: "lease", refLabelField: "einheit" },
|
|
278
|
+
],
|
|
272
279
|
},
|
|
273
280
|
{
|
|
274
281
|
id: "notes",
|
|
@@ -497,3 +504,17 @@ export const leaseListEmptyScreen: EntityListScreenDefinition = {
|
|
|
497
504
|
rowActions: [openLeaseDetail],
|
|
498
505
|
access: OPEN_ACCESS,
|
|
499
506
|
};
|
|
507
|
+
|
|
508
|
+
// Reference column next to empty plain cells: the predecessor links to its
|
|
509
|
+
// lease, a lease without one shows the empty-value placeholder.
|
|
510
|
+
export const leaseSuccessionListScreen: EntityListScreenDefinition = {
|
|
511
|
+
id: "lease-succession-list",
|
|
512
|
+
type: "entityList",
|
|
513
|
+
entity: "lease",
|
|
514
|
+
dormant: true,
|
|
515
|
+
columns: ["mieter", "vorgaenger", "ende", "beginn"],
|
|
516
|
+
defaultSort: { field: "beginn", dir: "desc" },
|
|
517
|
+
pageSize: 25,
|
|
518
|
+
rowActions: [openLeaseDetail],
|
|
519
|
+
access: OPEN_ACCESS,
|
|
520
|
+
};
|
|
@@ -30,7 +30,9 @@
|
|
|
30
30
|
"react-dom": "^19.2.6",
|
|
31
31
|
"zod": "^4.4.3",
|
|
32
32
|
"@cosmicdrift/kumiko-locale-de": "workspace:*",
|
|
33
|
-
"@cosmicdrift/kumiko-locale-es": "workspace:*"
|
|
33
|
+
"@cosmicdrift/kumiko-locale-es": "workspace:*",
|
|
34
|
+
"@cosmicdrift/kumiko-locale-fr": "workspace:*",
|
|
35
|
+
"@cosmicdrift/kumiko-locale-it": "workspace:*"
|
|
34
36
|
},
|
|
35
37
|
"devDependencies": {
|
|
36
38
|
"@cosmicdrift/kumiko-testing": "workspace:*"
|
|
@@ -12,9 +12,6 @@ import {
|
|
|
12
12
|
import { failNotFound } from "@cosmicdrift/kumiko-framework/errors";
|
|
13
13
|
import { z } from "zod";
|
|
14
14
|
|
|
15
|
-
// detail() returns an untyped row; parse the two fields the increment needs once.
|
|
16
|
-
const counterVersionRowSchema = z.object({ count: z.number(), version: z.number() });
|
|
17
|
-
|
|
18
15
|
export const counterEntity = createEntity({
|
|
19
16
|
table: "read_sample_counters",
|
|
20
17
|
fields: {
|
|
@@ -51,8 +48,9 @@ export const counterFeature = defineFeature("counters", (r) => {
|
|
|
51
48
|
return failNotFound("counter", event.payload.id);
|
|
52
49
|
}
|
|
53
50
|
|
|
54
|
-
|
|
55
|
-
const
|
|
51
|
+
// detail() returns the entity's own row type; count is nullable because the field is not required.
|
|
52
|
+
const { count, version } = current;
|
|
53
|
+
const newCount = (count ?? 0) + event.payload.amount;
|
|
56
54
|
// Read-modify-write on counter: use the version we just read so two
|
|
57
55
|
// concurrent increments don't clobber each other's changes.
|
|
58
56
|
return counterExecutor.update(
|
|
@@ -99,7 +99,7 @@ export const prospectsFeature = defineFeature("prospects", (r) => {
|
|
|
99
99
|
const flipped = await suggestionExecutor.update(
|
|
100
100
|
{
|
|
101
101
|
id: event.payload.suggestionId,
|
|
102
|
-
version: suggestion["version"]
|
|
102
|
+
version: suggestion["version"],
|
|
103
103
|
changes: { status: "accepted" },
|
|
104
104
|
},
|
|
105
105
|
event.user,
|
|
@@ -6,7 +6,7 @@ A write handler exports tenant data as a PDF and keeps it as a FileRef.
|
|
|
6
6
|
|
|
7
7
|
- `renderTablePdf` (`@cosmicdrift/kumiko-bundled-features/renderer-pdf`) draws a table document in pure JavaScript: repeating header, weighted columns, page footer. The handler passes ready-formatted strings.
|
|
8
8
|
- The export is a `beforeTransaction` handler: it reads the rows (reads work in that phase), renders the PDF and calls `uploadFileBytesBeforeTransaction` (`@cosmicdrift/kumiko-framework/files`), which validates the bytes and writes them to the tenant-prefixed storage key. The upload runs before the transaction because storage I/O inside it would hold a pool connection for as long as the upload takes.
|
|
9
|
-
- The handler itself calls `createFileRefFromUploadedBytes(ctx, event.prepared)` and creates the `file_refs` row and the `fileRef.created` event in the transaction. If the transaction does not commit, the phase's `release` deletes the uploaded bytes again (only when no row exists for the storage key). A crash between upload and commit leaves the bytes in storage;
|
|
9
|
+
- The handler itself calls `createFileRefFromUploadedBytes(ctx, event.prepared)` and creates the `file_refs` row and the `fileRef.created` event in the transaction. If the transaction does not commit, the phase's `release` deletes the uploaded bytes again (only when no row exists for the storage key). A crash between upload and commit leaves the bytes in storage; the hourly `files:job:sweep-orphaned-uploads` job deletes them once they are older than the grace period (24 hours by default, `uploadSweepGraceHours`).
|
|
10
10
|
- The result is a normal FileRef: `GET /api/files/:id` serves it with `Content-Type: application/pdf`, other tenants get 404.
|
|
11
11
|
|
|
12
12
|
## Source
|
|
@@ -75,6 +75,28 @@ Step-by-step proof:
|
|
|
75
75
|
2. Deletion + expired grace → `runForgetCleanup` removes all notes
|
|
76
76
|
3. Strategy `anonymize` → `authorId=null`, title/body remain
|
|
77
77
|
|
|
78
|
+
## Retention with files
|
|
79
|
+
|
|
80
|
+
`runRetentionCleanup` is exported for app tests. A `hardDelete` entity with a file field loses
|
|
81
|
+
its bytes (and derivatives), its `file_refs` row and then the row itself; a storage error keeps
|
|
82
|
+
the row and reports `file_delete_failed` in `skipped`. `src/retained-docs.ts` holds the entity,
|
|
83
|
+
`src/__tests__/retention-files.integration.test.ts` drives the run with only public imports:
|
|
84
|
+
|
|
85
|
+
```ts illustration
|
|
86
|
+
import { runRetentionCleanup } from "@cosmicdrift/kumiko-bundled-features/data-retention";
|
|
87
|
+
import { createFileContext } from "@cosmicdrift/kumiko-framework/files";
|
|
88
|
+
|
|
89
|
+
const result = await runRetentionCleanup({
|
|
90
|
+
db: stack.db,
|
|
91
|
+
registry: stack.registry,
|
|
92
|
+
tenantId,
|
|
93
|
+
preloadedTenantPreset: null,
|
|
94
|
+
now: getTemporal().Now.instant().add({ hours: 31 * 24 }), // keepFor 30d + 1 day
|
|
95
|
+
files: createFileContext(() => Promise.resolve(provider)), // the stack's storage provider
|
|
96
|
+
});
|
|
97
|
+
// result.hardDeleted === 1, bytes and file_refs row gone
|
|
98
|
+
```
|
|
99
|
+
|
|
78
100
|
## Full app vs this recipe
|
|
79
101
|
|
|
80
102
|
| | This recipe | [user-data-rights-demo](/en/samples/apps-user-data-rights-demo/) |
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// Retention with files: a hardDelete entity whose file field is cleaned up together with the
|
|
2
|
+
// row. runRetentionCleanup removes the bytes (and derivatives) and the file_refs row first,
|
|
3
|
+
// then the row itself; the cron in data-retention does the same with the tenant's FileContext.
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
createEntity,
|
|
7
|
+
createFileField,
|
|
8
|
+
createTextField,
|
|
9
|
+
defineEntityCreateHandler,
|
|
10
|
+
defineEntityDetailHandler,
|
|
11
|
+
defineFeature,
|
|
12
|
+
} from "@cosmicdrift/kumiko-framework/engine";
|
|
13
|
+
|
|
14
|
+
export const retainedDocEntity = createEntity({
|
|
15
|
+
table: "store_retained_docs",
|
|
16
|
+
fields: {
|
|
17
|
+
title: createTextField({ personal: false, reason: "is_business_data", required: true }),
|
|
18
|
+
attachment: createFileField(),
|
|
19
|
+
},
|
|
20
|
+
retention: { keepFor: "30d", strategy: "hardDelete", reference: "createdAt" },
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
export const retainedDocsFeature = defineFeature("retained-docs", (r) => {
|
|
24
|
+
r.entity({ name: "retained-doc", ...retainedDocEntity });
|
|
25
|
+
r.writeHandler(
|
|
26
|
+
defineEntityCreateHandler("retained-doc", retainedDocEntity, {
|
|
27
|
+
access: { roles: ["Admin"] },
|
|
28
|
+
}),
|
|
29
|
+
);
|
|
30
|
+
r.queryHandler(
|
|
31
|
+
defineEntityDetailHandler("retained-doc", retainedDocEntity, {
|
|
32
|
+
access: { roles: ["Admin"] },
|
|
33
|
+
}),
|
|
34
|
+
);
|
|
35
|
+
});
|