@cosmicdrift/kumiko-framework 0.2.2 → 0.3.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/CHANGELOG.md +54 -0
- package/package.json +124 -38
- package/src/__tests__/full-stack.integration.ts +2 -2
- package/src/api/auth-routes.ts +5 -5
- package/src/api/jwt.ts +2 -2
- package/src/api/route-registrars.ts +1 -1
- package/src/api/routes.ts +3 -3
- package/src/api/server.ts +6 -7
- package/src/auth/__tests__/roles.test.ts +24 -0
- package/src/auth/index.ts +7 -0
- package/src/auth/roles.ts +42 -0
- package/src/compliance/__tests__/duration-spec.test.ts +72 -0
- package/src/compliance/__tests__/profiles.test.ts +308 -0
- package/src/compliance/__tests__/sub-processors.test.ts +139 -0
- package/src/compliance/duration-spec.ts +44 -0
- package/src/compliance/index.ts +31 -0
- package/src/compliance/override-schema.ts +136 -0
- package/src/compliance/profiles.ts +427 -0
- package/src/compliance/sub-processors.ts +152 -0
- package/src/db/__tests__/big-int-field.test.ts +131 -0
- package/src/db/assert-exists-in.ts +2 -2
- package/src/db/cursor.ts +3 -3
- package/src/db/event-store-executor.ts +19 -13
- package/src/db/located-timestamp.ts +1 -1
- package/src/db/money.ts +12 -2
- package/src/db/pg-error.ts +1 -1
- package/src/db/row-helpers.ts +1 -1
- package/src/db/table-builder.ts +20 -5
- package/src/db/tenant-db.ts +9 -9
- package/src/engine/__tests__/_pipeline-test-utils.ts +23 -0
- package/src/engine/__tests__/boot-validator-api-exposure.test.ts +142 -0
- package/src/engine/__tests__/boot-validator-pii-retention.test.ts +570 -0
- package/src/engine/__tests__/boot-validator-s0-integration.test.ts +160 -0
- package/src/engine/__tests__/build-target.test.ts +135 -0
- package/src/engine/__tests__/codemod-pipeline.test.ts +551 -0
- package/src/engine/__tests__/entity-handlers.test.ts +3 -3
- package/src/engine/__tests__/event-helpers.test.ts +4 -4
- package/src/engine/__tests__/pipeline-engine.test.ts +215 -0
- package/src/engine/__tests__/pipeline-handler.integration.ts +894 -0
- package/src/engine/__tests__/pipeline-observability.integration.ts +142 -0
- package/src/engine/__tests__/pipeline-performance.integration.ts +152 -0
- package/src/engine/__tests__/pipeline-sub-pipelines.test.ts +288 -0
- package/src/engine/__tests__/raw-table.test.ts +2 -2
- package/src/engine/__tests__/steps-aggregate-append-event.test.ts +115 -0
- package/src/engine/__tests__/steps-aggregate-create.test.ts +92 -0
- package/src/engine/__tests__/steps-aggregate-update.test.ts +127 -0
- package/src/engine/__tests__/steps-call-feature.test.ts +123 -0
- package/src/engine/__tests__/steps-mail-send.test.ts +136 -0
- package/src/engine/__tests__/steps-read.test.ts +142 -0
- package/src/engine/__tests__/steps-resolver-utils.test.ts +50 -0
- package/src/engine/__tests__/steps-unsafe-projection-delete.test.ts +69 -0
- package/src/engine/__tests__/steps-unsafe-projection-upsert.test.ts +117 -0
- package/src/engine/__tests__/steps-webhook-send.test.ts +135 -0
- package/src/engine/__tests__/steps-workflow.test.ts +198 -0
- package/src/engine/__tests__/validate-projection-allowlist.test.ts +491 -0
- package/src/engine/__tests__/visual-tree-patterns.test.ts +251 -0
- package/src/engine/boot-validator/api-ext.ts +77 -0
- package/src/engine/boot-validator/config-deps.ts +163 -0
- package/src/engine/boot-validator/entity-handler.ts +466 -0
- package/src/engine/boot-validator/index.ts +159 -0
- package/src/engine/boot-validator/ownership.ts +198 -0
- package/src/engine/boot-validator/pii-retention.ts +155 -0
- package/src/engine/boot-validator/screens-nav.ts +624 -0
- package/src/engine/boot-validator.ts +1 -1528
- package/src/engine/build-app-schema.ts +1 -1
- package/src/engine/build-target.ts +99 -0
- package/src/engine/codemod/index.ts +15 -0
- package/src/engine/codemod/pipeline-codemod.ts +641 -0
- package/src/engine/config-helpers.ts +9 -19
- package/src/engine/constants.ts +1 -1
- package/src/engine/define-feature.ts +127 -9
- package/src/engine/define-handler.ts +89 -3
- package/src/engine/define-roles.ts +2 -2
- package/src/engine/define-step.ts +28 -0
- package/src/engine/define-workflow.ts +110 -0
- package/src/engine/entity-handlers.ts +10 -9
- package/src/engine/event-helpers.ts +4 -4
- package/src/engine/extension-names.ts +105 -0
- package/src/engine/extensions/user-data.ts +106 -0
- package/src/engine/factories.ts +26 -16
- package/src/engine/feature-ast/__tests__/visual-tree-parse.test.ts +184 -0
- package/src/engine/feature-ast/extractors/index.ts +74 -0
- package/src/engine/feature-ast/extractors/round1.ts +110 -0
- package/src/engine/feature-ast/extractors/round2.ts +253 -0
- package/src/engine/feature-ast/extractors/round3.ts +471 -0
- package/src/engine/feature-ast/extractors/round4.ts +1365 -0
- package/src/engine/feature-ast/extractors/round5.ts +72 -0
- package/src/engine/feature-ast/extractors/round6.ts +66 -0
- package/src/engine/feature-ast/extractors/shared.ts +177 -0
- package/src/engine/feature-ast/parse.ts +13 -0
- package/src/engine/feature-ast/patch.ts +9 -1
- package/src/engine/feature-ast/patcher.ts +10 -3
- package/src/engine/feature-ast/patterns.ts +71 -1
- package/src/engine/feature-ast/render.ts +31 -1
- package/src/engine/index.ts +66 -2
- package/src/engine/pattern-library/__tests__/library.test.ts +11 -0
- package/src/engine/pattern-library/library.ts +78 -2
- package/src/engine/pipeline.ts +88 -0
- package/src/engine/projection-helpers.ts +1 -1
- package/src/engine/read-claim.ts +1 -1
- package/src/engine/registry.ts +30 -2
- package/src/engine/resolve-config-or-param.ts +4 -0
- package/src/engine/run-pipeline.ts +162 -0
- package/src/engine/schema-builder.ts +10 -4
- package/src/engine/state-machine.ts +1 -1
- package/src/engine/steps/_drizzle-boundary.ts +19 -0
- package/src/engine/steps/_duration-utils.ts +33 -0
- package/src/engine/steps/_no-return-guard.ts +21 -0
- package/src/engine/steps/_resolver-utils.ts +42 -0
- package/src/engine/steps/_step-dispatch-constants.ts +38 -0
- package/src/engine/steps/aggregate-append-event.ts +56 -0
- package/src/engine/steps/aggregate-create.ts +56 -0
- package/src/engine/steps/aggregate-update.ts +68 -0
- package/src/engine/steps/branch.ts +84 -0
- package/src/engine/steps/call-feature.ts +49 -0
- package/src/engine/steps/compute.ts +41 -0
- package/src/engine/steps/for-each.ts +111 -0
- package/src/engine/steps/mail-send.ts +44 -0
- package/src/engine/steps/read-find-many.ts +51 -0
- package/src/engine/steps/read-find-one.ts +58 -0
- package/src/engine/steps/retry.ts +87 -0
- package/src/engine/steps/return.ts +34 -0
- package/src/engine/steps/unsafe-projection-delete.ts +46 -0
- package/src/engine/steps/unsafe-projection-upsert.ts +69 -0
- package/src/engine/steps/wait-for-event.ts +71 -0
- package/src/engine/steps/wait.ts +69 -0
- package/src/engine/steps/webhook-send.ts +71 -0
- package/src/engine/system-user.ts +1 -1
- package/src/engine/types/feature.ts +143 -1
- package/src/engine/types/fields.ts +134 -10
- package/src/engine/types/handlers.ts +18 -10
- package/src/engine/types/identifiers.ts +1 -0
- package/src/engine/types/index.ts +15 -1
- package/src/engine/types/step.ts +334 -0
- package/src/engine/types/target-ref.ts +21 -0
- package/src/engine/types/tree-node.ts +130 -0
- package/src/engine/types/workspace.ts +7 -0
- package/src/engine/validate-projection-allowlist.ts +161 -0
- package/src/event-store/snapshot.ts +1 -1
- package/src/event-store/upcaster-dead-letter.ts +1 -1
- package/src/event-store/upcaster.ts +1 -1
- package/src/files/__tests__/read-stream.test.ts +105 -0
- package/src/files/__tests__/write-stream.test.ts +233 -0
- package/src/files/__tests__/zip-stream.test.ts +357 -0
- package/src/files/file-routes.ts +1 -1
- package/src/files/in-memory-provider.ts +38 -0
- package/src/files/index.ts +3 -0
- package/src/files/local-provider.ts +58 -1
- package/src/files/types.ts +36 -8
- package/src/files/zip-stream.ts +251 -0
- package/src/jobs/job-runner.ts +10 -10
- package/src/lifecycle/lifecycle.ts +0 -3
- package/src/logging/index.ts +1 -0
- package/src/logging/pino-logger.ts +11 -7
- package/src/logging/utils.ts +24 -0
- package/src/observability/prometheus-meter.ts +7 -5
- package/src/pipeline/__tests__/archive-stream.integration.ts +1 -1
- package/src/pipeline/__tests__/causation-chain.integration.ts +1 -1
- package/src/pipeline/__tests__/domain-events-projections.integration.ts +3 -3
- package/src/pipeline/__tests__/event-define-event-strict.integration.ts +4 -4
- package/src/pipeline/__tests__/load-aggregate-query.integration.ts +1 -1
- package/src/pipeline/__tests__/msp-multi-hop.integration.ts +3 -3
- package/src/pipeline/__tests__/msp-rebuild.integration.ts +3 -3
- package/src/pipeline/__tests__/multi-stream-projection.integration.ts +2 -2
- package/src/pipeline/__tests__/query-projection.integration.ts +5 -5
- package/src/pipeline/append-event-core.ts +22 -6
- package/src/pipeline/dispatcher-utils.ts +188 -0
- package/src/pipeline/dispatcher.ts +63 -283
- package/src/pipeline/distributed-lock.ts +1 -1
- package/src/pipeline/entity-cache.ts +2 -2
- package/src/pipeline/event-consumer-state.ts +0 -13
- package/src/pipeline/event-dispatcher.ts +4 -4
- package/src/pipeline/index.ts +0 -2
- package/src/pipeline/lifecycle-pipeline.ts +6 -12
- package/src/pipeline/msp-rebuild.ts +5 -5
- package/src/pipeline/multi-stream-apply-context.ts +6 -7
- package/src/pipeline/projection-rebuild.ts +2 -2
- package/src/pipeline/projection-state.ts +0 -12
- package/src/rate-limit/__tests__/resolver.integration.ts +8 -4
- package/src/rate-limit/resolver.ts +1 -1
- package/src/search/in-memory-adapter.ts +1 -1
- package/src/search/meilisearch-adapter.ts +3 -3
- package/src/search/types.ts +1 -1
- package/src/secrets/leak-guard.ts +2 -2
- package/src/stack/request-helper.ts +9 -5
- package/src/stack/test-stack.ts +1 -1
- package/src/testing/handler-context.ts +4 -4
- package/src/testing/http-cookies.ts +1 -1
- package/src/time/tz-context.ts +1 -2
- package/src/ui-types/index.ts +4 -0
- package/src/engine/feature-ast/extractors.ts +0 -2562
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
// Standardisierte Extension-Namen fuer Datenschutz-Hook-Achsen.
|
|
2
|
+
//
|
|
3
|
+
// Features registrieren Extensions via:
|
|
4
|
+
// r.extendsRegistrar(EXT_USER_DATA, { hooks: { ... } });
|
|
5
|
+
//
|
|
6
|
+
// Andere Features haengen sich ein via:
|
|
7
|
+
// r.useExtension(EXT_USER_DATA, "myEntity", { ...hookImpls });
|
|
8
|
+
//
|
|
9
|
+
// Hintergrund: Magic-Strings driften zwischen Bundled-Features (Beispiel:
|
|
10
|
+
// text-content nutzt "Admin" als Rolle, tenant-handler nutzt "TenantAdmin").
|
|
11
|
+
// Constants sind die einzige Quelle der Wahrheit; String-Literale werden
|
|
12
|
+
// in den Sprint-Touchpoints schrittweise ersetzt. Boot-Validator
|
|
13
|
+
// (validateExtensionUsages) checkt dass jedes useExtension einen
|
|
14
|
+
// passenden extendsRegistrar findet — Tippfehler in Constants →
|
|
15
|
+
// Compile-Time-Fail.
|
|
16
|
+
//
|
|
17
|
+
// Hook-Signaturen + Boot-Validation pro Extension-Achse kommen mit dem
|
|
18
|
+
// jeweiligen registrierenden Sprint:
|
|
19
|
+
// userData / tenantData → Sprint 2 (user-data-rights, retention)
|
|
20
|
+
// storageProvider → Sprint 4 (storage-encryption)
|
|
21
|
+
// searchAdapter / external / → Sprint 5 (tenant-lifecycle)
|
|
22
|
+
// infraResource
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* `userData` — User-Daten-Rights-Hooks (DSGVO Art. 15 + 17 + 20).
|
|
26
|
+
*
|
|
27
|
+
* Erwartete Hook-Methoden:
|
|
28
|
+
* - `export(userId, ctx) => Promise<UserDataExport>`
|
|
29
|
+
* - `delete(userId, strategy: "delete" | "anonymize", ctx) => Promise<void>`
|
|
30
|
+
*
|
|
31
|
+
* Registriert von: `user-data-rights` (Sprint 2).
|
|
32
|
+
* Genutzt von: jedes Feature mit User-Referenzen (tasks, comments, files, ...).
|
|
33
|
+
*/
|
|
34
|
+
export const EXT_USER_DATA = "userData" as const;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* `tenantData` — Tenant-Destroy-Hooks pro Entity (DSGVO + AVV-Beendigung).
|
|
38
|
+
*
|
|
39
|
+
* Erwartete Hook-Methoden:
|
|
40
|
+
* - `destroy(tenantId, ctx) => Promise<void>`
|
|
41
|
+
*
|
|
42
|
+
* Registriert von: `tenant-lifecycle` (Sprint 5).
|
|
43
|
+
* Genutzt von: jedes Feature mit tenantId-Field.
|
|
44
|
+
*/
|
|
45
|
+
export const EXT_TENANT_DATA = "tenantData" as const;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* `storageProvider` — File-Storage-Plugin-Hooks (Crypto-Shredding fuer Files).
|
|
49
|
+
*
|
|
50
|
+
* Erwartete Hook-Methoden:
|
|
51
|
+
* - `destroyTenant(tenantId, ctx) => Promise<void>`
|
|
52
|
+
* - `destroySubject(subject, ctx) => Promise<{ deleted: number }>`
|
|
53
|
+
*
|
|
54
|
+
* Registriert von: `storage-encryption` (Sprint 4).
|
|
55
|
+
* Genutzt von: pluggable Provider (Local, MinIO, S3, R2).
|
|
56
|
+
*/
|
|
57
|
+
export const EXT_STORAGE_PROVIDER = "storageProvider" as const;
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* `searchAdapter` — Search-Adapter-Forget-Hooks (Meilisearch-Index-Cleanup
|
|
61
|
+
* bei User-Forget oder Tenant-Destroy).
|
|
62
|
+
*
|
|
63
|
+
* Erwartete Hook-Methoden:
|
|
64
|
+
* - `destroyTenant(tenantId, ctx) => Promise<void>`
|
|
65
|
+
* - `eraseSubject(subject, ctx) => Promise<void>`
|
|
66
|
+
*
|
|
67
|
+
* Registriert von: `tenant-lifecycle` (Sprint 5).
|
|
68
|
+
* Genutzt von: Meilisearch- und andere Search-Adapter-Implementierungen.
|
|
69
|
+
*/
|
|
70
|
+
export const EXT_SEARCH_ADAPTER = "searchAdapter" as const;
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* `externalResource` — External-Service-Tenant-Cleanup
|
|
74
|
+
* (Webhook-Subscriptions, Brevo-Empfaenger-Listen, Stripe-Customer-Account).
|
|
75
|
+
*
|
|
76
|
+
* Erwartete Hook-Methoden:
|
|
77
|
+
* - `destroyTenant(tenantId, ctx) => Promise<void>`
|
|
78
|
+
*
|
|
79
|
+
* Registriert von: `tenant-lifecycle` (Sprint 5).
|
|
80
|
+
*/
|
|
81
|
+
export const EXT_EXTERNAL_RESOURCE = "externalResource" as const;
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* `infraResource` — Pulumi-managed Resources pro Tenant
|
|
85
|
+
* (Custom-Domain, Cert-Manager-Issuer, dedicated Pod/Volume).
|
|
86
|
+
*
|
|
87
|
+
* Erwartete Hook-Methoden:
|
|
88
|
+
* - `destroyTenant(tenantId, ctx) => Promise<void>`
|
|
89
|
+
*
|
|
90
|
+
* Registriert von: `tenant-lifecycle` (Sprint 5).
|
|
91
|
+
*/
|
|
92
|
+
export const EXT_INFRA_RESOURCE = "infraResource" as const;
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Union aller standardisierten Extension-Namen der Datenschutz-Surface.
|
|
96
|
+
* Nicht alle Extensions im System sind in dieser Liste — andere
|
|
97
|
+
* Features koennen weiterhin eigene Extension-Namen registrieren.
|
|
98
|
+
*/
|
|
99
|
+
export type KumikoExtensionName =
|
|
100
|
+
| typeof EXT_USER_DATA
|
|
101
|
+
| typeof EXT_TENANT_DATA
|
|
102
|
+
| typeof EXT_STORAGE_PROVIDER
|
|
103
|
+
| typeof EXT_SEARCH_ADAPTER
|
|
104
|
+
| typeof EXT_EXTERNAL_RESOURCE
|
|
105
|
+
| typeof EXT_INFRA_RESOURCE;
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
// Hook-Signatur-Types für die EXT_USER_DATA-Extension (DSGVO Art. 15+17+20).
|
|
2
|
+
//
|
|
3
|
+
// Sprint 1.9 Z1: Bisher gibt der Boot-Validator JEDES Hook-Shape durch
|
|
4
|
+
// — useExtension(EXT_USER_DATA, "X", { export: ... }) wird nicht gegen
|
|
5
|
+
// eine erwartete Signatur geprüft. Diese Types sind die canonical
|
|
6
|
+
// Schema-Sicht; Sprint 2 user-data-rights wird sie via
|
|
7
|
+
// `r.extendsRegistrar(EXT_USER_DATA, { hooks: ... })`-Doku exposen.
|
|
8
|
+
//
|
|
9
|
+
// Boot-Time-Schape-Check (Runtime) ist orthogonal und kommt in Sprint
|
|
10
|
+
// 2 wenn die exportRunner-/forgetRunner-Pipelines stehen — bis dahin
|
|
11
|
+
// sind diese Types Compile-Time-Hints für App-Authors, keine Runtime-
|
|
12
|
+
// Validation.
|
|
13
|
+
//
|
|
14
|
+
// Siehe docs/plans/datenschutz/user-data-rights.md.
|
|
15
|
+
|
|
16
|
+
import type { DbRunner } from "../../db/connection";
|
|
17
|
+
import type { TenantId } from "../types";
|
|
18
|
+
|
|
19
|
+
// SessionUser.id ist plattformweit `string` (kein Brand-Type). Wenn
|
|
20
|
+
// jemals ein UserId-Brand eingefuehrt wird, ersetzt man hier den
|
|
21
|
+
// inline-Type — andere Codebase-Stellen nutzen denselben Pfad.
|
|
22
|
+
type UserId = string;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Strategie für den Forget-Pfad pro Entity:
|
|
26
|
+
* - "delete": Row physisch entfernen (Profil, Eigene Notizen, Sessions).
|
|
27
|
+
* - "anonymize": User-Reference auf null + Display-Felder auf
|
|
28
|
+
* "[Geloescht]" — typisch für geteilte Daten (Tasks,
|
|
29
|
+
* Comments) damit andere User die History nicht verlieren.
|
|
30
|
+
*
|
|
31
|
+
* Cleanup-Job (Sprint 2 data-retention) entscheidet pro Entity über
|
|
32
|
+
* Retention-Policy, welche Strategie greift. blockDelete-Entries lösen
|
|
33
|
+
* IMMER `anonymize` aus damit Aufbewahrungs-Pflicht respektiert wird.
|
|
34
|
+
*/
|
|
35
|
+
export type UserDataDeleteStrategy = "delete" | "anonymize";
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Context-Snapshot der dem Hook übergeben wird. Sprint 2 erweitert
|
|
39
|
+
* das ggf. um cancel-/timeout-Marker; aktuell minimaler Schnitt.
|
|
40
|
+
*
|
|
41
|
+
* `db` ist `DbRunner` (DbConnection | DbTx) damit der Cleanup-Runner
|
|
42
|
+
* (S2.U5b) den Hook in einer Per-User-Sub-Tx callen kann. Hooks die
|
|
43
|
+
* raw-DB-Operationen machen funktionieren auf beiden Shapes via
|
|
44
|
+
* Drizzle's polymorphem select/insert/update/delete-Chain.
|
|
45
|
+
*/
|
|
46
|
+
export interface UserDataHookCtx {
|
|
47
|
+
readonly db: DbRunner;
|
|
48
|
+
readonly tenantId: TenantId;
|
|
49
|
+
readonly userId: UserId;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Pro Feature/Entity-Snippet das im Export-Bundle landet. Sprint 2
|
|
54
|
+
* orchestriert die JSON-Serialisierung; Hooks geben Plain-Records.
|
|
55
|
+
*/
|
|
56
|
+
export interface UserDataExportSnippet {
|
|
57
|
+
readonly entity: string;
|
|
58
|
+
readonly rows: ReadonlyArray<Record<string, unknown>>;
|
|
59
|
+
/**
|
|
60
|
+
* Optional: signed-URLs für File-Refs. user-data-rights packt sie
|
|
61
|
+
* separat ins ZIP unter `files/`. Andere Hooks lassen das leer.
|
|
62
|
+
*/
|
|
63
|
+
readonly fileRefs?: ReadonlyArray<{
|
|
64
|
+
readonly fileRefId: string;
|
|
65
|
+
readonly storageKey: string;
|
|
66
|
+
readonly fileName: string;
|
|
67
|
+
}>;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Export-Hook: Sammelt alle Daten einer Entity die zu einem User
|
|
72
|
+
* gehören. Wird im Daten-Export-Job pro registrierter Entity einmal
|
|
73
|
+
* aufgerufen. Idempotent — kann mehrfach aufgerufen werden ohne
|
|
74
|
+
* Side-Effects.
|
|
75
|
+
*
|
|
76
|
+
* Sprint 2 user-data-rights ruft das via Iteration über alle
|
|
77
|
+
* `r.useExtension(EXT_USER_DATA, ...)`-Registrierungen.
|
|
78
|
+
*/
|
|
79
|
+
export type UserDataExportHook = (ctx: UserDataHookCtx) => Promise<UserDataExportSnippet | null>;
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Forget-Hook: Löscht oder anonymisiert die Entity-Rows die zu einem
|
|
83
|
+
* User gehören. Strategy kommt vom Cleanup-Job (kann per Entity
|
|
84
|
+
* unterschiedlich sein wegen Retention-Policy).
|
|
85
|
+
*
|
|
86
|
+
* Idempotent — wenn der Job zweimal läuft (Crash-Recovery), darf der
|
|
87
|
+
* Hook nicht crashen.
|
|
88
|
+
*/
|
|
89
|
+
export type UserDataDeleteHook = (
|
|
90
|
+
ctx: UserDataHookCtx,
|
|
91
|
+
strategy: UserDataDeleteStrategy,
|
|
92
|
+
) => Promise<void>;
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Komplette Hook-Tafel für EXT_USER_DATA. Sprint 2 user-data-rights
|
|
96
|
+
* deklariert das via `r.extendsRegistrar(EXT_USER_DATA, { hooks: ... })`,
|
|
97
|
+
* konsumierende Features liefern beide Hooks via
|
|
98
|
+
* `r.useExtension(EXT_USER_DATA, "<entity>", { export, delete })`.
|
|
99
|
+
*
|
|
100
|
+
* Kein Hook ist optional — beide MÜSSEN registriert sein. Boot-Check
|
|
101
|
+
* (Sprint 2) prüft das.
|
|
102
|
+
*/
|
|
103
|
+
export interface UserDataExtensionHooks {
|
|
104
|
+
readonly export: UserDataExportHook;
|
|
105
|
+
readonly delete: UserDataDeleteHook;
|
|
106
|
+
}
|
package/src/engine/factories.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import type {
|
|
2
|
+
BigIntFieldDef,
|
|
2
3
|
BooleanFieldDef,
|
|
3
4
|
DateFieldDef,
|
|
4
5
|
EmbeddedFieldDef,
|
|
5
6
|
EntityDefinition,
|
|
7
|
+
EntityIndexDef,
|
|
6
8
|
FieldsMap,
|
|
7
9
|
FileFieldDef,
|
|
8
10
|
FilesFieldDef,
|
|
@@ -13,6 +15,7 @@ import type {
|
|
|
13
15
|
MoneyFieldDef,
|
|
14
16
|
MultiSelectFieldDef,
|
|
15
17
|
NumberFieldDef,
|
|
18
|
+
RetentionDef,
|
|
16
19
|
SelectFieldDef,
|
|
17
20
|
TextFieldDef,
|
|
18
21
|
TimestampFieldDef,
|
|
@@ -36,7 +39,7 @@ export function createTextField<R extends true | false = false>(
|
|
|
36
39
|
searchable: false,
|
|
37
40
|
sortable: false,
|
|
38
41
|
...overrides,
|
|
39
|
-
} as TextFieldDef & { required: R };
|
|
42
|
+
} as TextFieldDef & { required: R }; // @cast-boundary engine-payload
|
|
40
43
|
}
|
|
41
44
|
|
|
42
45
|
/**
|
|
@@ -56,7 +59,7 @@ export function createLongTextField<R extends true | false = false>(
|
|
|
56
59
|
type: "longText",
|
|
57
60
|
required: false,
|
|
58
61
|
...overrides,
|
|
59
|
-
} as LongTextFieldDef & { required: R };
|
|
62
|
+
} as LongTextFieldDef & { required: R }; // @cast-boundary engine-payload
|
|
60
63
|
}
|
|
61
64
|
|
|
62
65
|
export function createBooleanField<R extends true | false = false>(
|
|
@@ -67,7 +70,7 @@ export function createBooleanField<R extends true | false = false>(
|
|
|
67
70
|
required: false,
|
|
68
71
|
default: false,
|
|
69
72
|
...overrides,
|
|
70
|
-
} as BooleanFieldDef & { required: R };
|
|
73
|
+
} as BooleanFieldDef & { required: R }; // @cast-boundary engine-payload
|
|
71
74
|
}
|
|
72
75
|
|
|
73
76
|
export function createSelectField<
|
|
@@ -82,7 +85,7 @@ export function createSelectField<
|
|
|
82
85
|
type: "select",
|
|
83
86
|
required: false,
|
|
84
87
|
...opts,
|
|
85
|
-
} as SelectFieldDef<TOptions> & { required: R };
|
|
88
|
+
} as SelectFieldDef<TOptions> & { required: R }; // @cast-boundary engine-payload
|
|
86
89
|
}
|
|
87
90
|
|
|
88
91
|
/**
|
|
@@ -123,7 +126,17 @@ export function createNumberField<R extends true | false = false>(
|
|
|
123
126
|
type: "number",
|
|
124
127
|
required: false,
|
|
125
128
|
...overrides,
|
|
126
|
-
} as NumberFieldDef & { required: R };
|
|
129
|
+
} as NumberFieldDef & { required: R }; // @cast-boundary engine-payload
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export function createBigIntField<R extends true | false = false>(
|
|
133
|
+
overrides?: Partial<Omit<BigIntFieldDef, "type" | "required">> & { required?: R },
|
|
134
|
+
): BigIntFieldDef & { required: R } {
|
|
135
|
+
return {
|
|
136
|
+
type: "bigInt",
|
|
137
|
+
required: false,
|
|
138
|
+
...overrides,
|
|
139
|
+
} as BigIntFieldDef & { required: R }; // @cast-boundary engine-payload
|
|
127
140
|
}
|
|
128
141
|
|
|
129
142
|
export function createMoneyField<R extends true | false = false>(
|
|
@@ -132,7 +145,7 @@ export function createMoneyField<R extends true | false = false>(
|
|
|
132
145
|
return {
|
|
133
146
|
type: "money",
|
|
134
147
|
...overrides,
|
|
135
|
-
} as MoneyFieldDef & { required: R };
|
|
148
|
+
} as MoneyFieldDef & { required: R }; // @cast-boundary engine-payload
|
|
136
149
|
}
|
|
137
150
|
|
|
138
151
|
export function createEmbeddedField(
|
|
@@ -153,7 +166,7 @@ export function createDateField<R extends true | false = false>(
|
|
|
153
166
|
type: "date",
|
|
154
167
|
required: false,
|
|
155
168
|
...overrides,
|
|
156
|
-
} as DateFieldDef & { required: R };
|
|
169
|
+
} as DateFieldDef & { required: R }; // @cast-boundary engine-payload
|
|
157
170
|
}
|
|
158
171
|
|
|
159
172
|
/**
|
|
@@ -173,7 +186,7 @@ export function createTimestampField<R extends true | false = false>(
|
|
|
173
186
|
return {
|
|
174
187
|
...overrides,
|
|
175
188
|
type: "timestamp",
|
|
176
|
-
required: (overrides?.required ?? false) as R,
|
|
189
|
+
required: (overrides?.required ?? false) as R, // @cast-boundary engine-payload
|
|
177
190
|
};
|
|
178
191
|
}
|
|
179
192
|
|
|
@@ -188,7 +201,7 @@ export function createTzField<R extends true | false = false>(
|
|
|
188
201
|
type: "tz",
|
|
189
202
|
required: false,
|
|
190
203
|
...overrides,
|
|
191
|
-
} as TzFieldDef & { required: R };
|
|
204
|
+
} as TzFieldDef & { required: R }; // @cast-boundary engine-payload
|
|
192
205
|
}
|
|
193
206
|
|
|
194
207
|
/**
|
|
@@ -228,7 +241,7 @@ export function createLocatedTimestampField<R extends true | false = false>(
|
|
|
228
241
|
type: "locatedTimestamp",
|
|
229
242
|
required: false,
|
|
230
243
|
...overrides,
|
|
231
|
-
} as LocatedTimestampFieldDef & { required: R };
|
|
244
|
+
} as LocatedTimestampFieldDef & { required: R }; // @cast-boundary engine-payload
|
|
232
245
|
}
|
|
233
246
|
|
|
234
247
|
/**
|
|
@@ -309,13 +322,10 @@ export function createEntity<F>(def: {
|
|
|
309
322
|
readonly searchWeight?: number;
|
|
310
323
|
readonly defaultCurrency?: string;
|
|
311
324
|
readonly transitions?: Readonly<Record<string, Readonly<Record<string, readonly string[]>>>>;
|
|
312
|
-
readonly indexes?: readonly
|
|
313
|
-
readonly columns: readonly [string, ...string[]];
|
|
314
|
-
readonly unique?: boolean;
|
|
315
|
-
readonly name?: string;
|
|
316
|
-
}[];
|
|
325
|
+
readonly indexes?: readonly EntityIndexDef[];
|
|
317
326
|
readonly idType?: "serial" | "uuid";
|
|
318
327
|
readonly access?: EntityDefinition["access"];
|
|
328
|
+
readonly retention?: RetentionDef;
|
|
319
329
|
}): F extends FieldsMap ? EntityDefinition<F> : never {
|
|
320
330
|
return {
|
|
321
331
|
softDelete: false,
|
|
@@ -324,5 +334,5 @@ export function createEntity<F>(def: {
|
|
|
324
334
|
// aggregate-ids are UUID. Opt-out with `idType: "serial"` for pre-ES
|
|
325
335
|
// legacy tables (should be rare).
|
|
326
336
|
...def,
|
|
327
|
-
} as F extends FieldsMap ? EntityDefinition<F> : never;
|
|
337
|
+
} as F extends FieldsMap ? EntityDefinition<F> : never; // @cast-boundary engine-payload
|
|
328
338
|
}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
// AST round-trip tests for the Visual-Tree patterns. Each test feeds
|
|
2
|
+
// a minimal inline-feature into parseSourceFile and asserts the
|
|
3
|
+
// extracted Pattern shape + the editability classification.
|
|
4
|
+
//
|
|
5
|
+
// Why split from visual-tree-patterns.test.ts (engine-level): that suite
|
|
6
|
+
// covers the registrar+registry runtime path. This suite covers the
|
|
7
|
+
// AST extractor + the Designer/AI consumers (renderPattern,
|
|
8
|
+
// getEditability, PATTERN_LIBRARY). Both halves must agree on shape.
|
|
9
|
+
|
|
10
|
+
import { Project } from "ts-morph";
|
|
11
|
+
import { describe, expect, test } from "vitest";
|
|
12
|
+
import { parseSourceFile } from "../parse";
|
|
13
|
+
import { getEditability } from "../patterns";
|
|
14
|
+
import { renderPattern } from "../render";
|
|
15
|
+
|
|
16
|
+
function parseInline(source: string) {
|
|
17
|
+
const project = new Project({ skipAddingFilesFromTsConfig: true, useInMemoryFileSystem: true });
|
|
18
|
+
const sourceFile = project.createSourceFile("feature.ts", source);
|
|
19
|
+
return parseSourceFile(sourceFile);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
describe("parseSourceFile — r.treeActions extraction", () => {
|
|
23
|
+
test("extracts a static treeActions pattern from an inline action-map", () => {
|
|
24
|
+
const result = parseInline(`
|
|
25
|
+
import { defineFeature } from "@cosmicdrift/kumiko-framework/engine";
|
|
26
|
+
defineFeature("text-content", (r) => {
|
|
27
|
+
r.treeActions({
|
|
28
|
+
edit: { args: { slug: "" as string } },
|
|
29
|
+
list: {},
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
`);
|
|
33
|
+
|
|
34
|
+
expect(result.errors).toEqual([]);
|
|
35
|
+
const treeActions = result.patterns.find((p) => p.kind === "treeActions");
|
|
36
|
+
expect(treeActions).toBeDefined();
|
|
37
|
+
expect(treeActions?.kind).toBe("treeActions");
|
|
38
|
+
if (treeActions?.kind === "treeActions") {
|
|
39
|
+
expect(treeActions.definitions).toEqual({
|
|
40
|
+
edit: { args: { slug: "" } },
|
|
41
|
+
list: {},
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test("treeActions pattern is classified as static (Designer renders form)", () => {
|
|
47
|
+
const result = parseInline(`
|
|
48
|
+
import { defineFeature } from "@cosmicdrift/kumiko-framework/engine";
|
|
49
|
+
defineFeature("x", (r) => { r.treeActions({ list: {} }); });
|
|
50
|
+
`);
|
|
51
|
+
const treeActions = result.patterns.find((p) => p.kind === "treeActions");
|
|
52
|
+
expect(treeActions).toBeDefined();
|
|
53
|
+
if (treeActions !== undefined) {
|
|
54
|
+
expect(getEditability(treeActions)).toBe("static");
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test("missing first argument produces a parse-error, not a pattern", () => {
|
|
59
|
+
const result = parseInline(`
|
|
60
|
+
import { defineFeature } from "@cosmicdrift/kumiko-framework/engine";
|
|
61
|
+
defineFeature("x", (r) => { r.treeActions(); });
|
|
62
|
+
`);
|
|
63
|
+
expect(result.patterns.find((p) => p.kind === "treeActions")).toBeUndefined();
|
|
64
|
+
expect(result.errors.some((e) => e.methodName === "treeActions")).toBe(true);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
test("non-object first argument (identifier ref) produces a parse-error", () => {
|
|
68
|
+
const result = parseInline(`
|
|
69
|
+
import { defineFeature } from "@cosmicdrift/kumiko-framework/engine";
|
|
70
|
+
const actions = { list: {} };
|
|
71
|
+
defineFeature("x", (r) => { r.treeActions(actions); });
|
|
72
|
+
`);
|
|
73
|
+
expect(result.patterns.find((p) => p.kind === "treeActions")).toBeUndefined();
|
|
74
|
+
expect(result.errors.some((e) => e.methodName === "treeActions")).toBe(true);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test("renderPattern round-trips back to a valid r.treeActions(...) call", () => {
|
|
78
|
+
const result = parseInline(`
|
|
79
|
+
import { defineFeature } from "@cosmicdrift/kumiko-framework/engine";
|
|
80
|
+
defineFeature("x", (r) => {
|
|
81
|
+
r.treeActions({ edit: { args: { slug: "" } }, list: {} });
|
|
82
|
+
});
|
|
83
|
+
`);
|
|
84
|
+
const treeActions = result.patterns.find((p) => p.kind === "treeActions");
|
|
85
|
+
expect(treeActions).toBeDefined();
|
|
86
|
+
if (treeActions !== undefined) {
|
|
87
|
+
const rendered = renderPattern(treeActions);
|
|
88
|
+
expect(rendered).toMatch(/^r\.treeActions\(/);
|
|
89
|
+
// renderValue darf identifier-safe Keys unquoted ausgeben — beide
|
|
90
|
+
// Schreibweisen sind valide TypeScript-Source.
|
|
91
|
+
expect(rendered).toMatch(/(["])edit\1|edit\s*:/);
|
|
92
|
+
expect(rendered).toMatch(/(["])slug\1|slug\s*:/);
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
describe("parseSourceFile — r.tree extraction", () => {
|
|
98
|
+
test("extracts an opaque tree pattern with the provider body as SourceLocation", () => {
|
|
99
|
+
const result = parseInline(`
|
|
100
|
+
import { defineFeature } from "@cosmicdrift/kumiko-framework/engine";
|
|
101
|
+
defineFeature("text-content", (r) => {
|
|
102
|
+
r.tree((ctx) => (emit) => {
|
|
103
|
+
emit([{ label: "Marketing" }]);
|
|
104
|
+
return () => {};
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
`);
|
|
108
|
+
|
|
109
|
+
expect(result.errors).toEqual([]);
|
|
110
|
+
const tree = result.patterns.find((p) => p.kind === "tree");
|
|
111
|
+
expect(tree).toBeDefined();
|
|
112
|
+
if (tree?.kind === "tree") {
|
|
113
|
+
expect(tree.providerBody.raw).toContain("emit");
|
|
114
|
+
expect(tree.providerBody.raw).toContain("Marketing");
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
test("tree pattern is classified as opaque (Designer renders read-only code-block)", () => {
|
|
119
|
+
const result = parseInline(`
|
|
120
|
+
import { defineFeature } from "@cosmicdrift/kumiko-framework/engine";
|
|
121
|
+
defineFeature("x", (r) => { r.tree((ctx) => (emit) => () => {}); });
|
|
122
|
+
`);
|
|
123
|
+
const tree = result.patterns.find((p) => p.kind === "tree");
|
|
124
|
+
expect(tree).toBeDefined();
|
|
125
|
+
if (tree !== undefined) {
|
|
126
|
+
expect(getEditability(tree)).toBe("opaque");
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
test("missing first argument produces a parse-error, not a pattern", () => {
|
|
131
|
+
const result = parseInline(`
|
|
132
|
+
import { defineFeature } from "@cosmicdrift/kumiko-framework/engine";
|
|
133
|
+
defineFeature("x", (r) => { r.tree(); });
|
|
134
|
+
`);
|
|
135
|
+
expect(result.patterns.find((p) => p.kind === "tree")).toBeUndefined();
|
|
136
|
+
expect(result.errors.some((e) => e.methodName === "tree")).toBe(true);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
test("non-function first argument (identifier ref) produces a parse-error", () => {
|
|
140
|
+
const result = parseInline(`
|
|
141
|
+
import { defineFeature } from "@cosmicdrift/kumiko-framework/engine";
|
|
142
|
+
const provider = (ctx: unknown) => (emit: unknown) => () => {};
|
|
143
|
+
defineFeature("x", (r) => { r.tree(provider); });
|
|
144
|
+
`);
|
|
145
|
+
expect(result.patterns.find((p) => p.kind === "tree")).toBeUndefined();
|
|
146
|
+
expect(result.errors.some((e) => e.methodName === "tree")).toBe(true);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
test("renderPattern round-trips back to a valid r.tree(...) call with the body verbatim", () => {
|
|
150
|
+
const result = parseInline(`
|
|
151
|
+
import { defineFeature } from "@cosmicdrift/kumiko-framework/engine";
|
|
152
|
+
defineFeature("x", (r) => {
|
|
153
|
+
r.tree((ctx) => (emit) => { emit([]); return () => {}; });
|
|
154
|
+
});
|
|
155
|
+
`);
|
|
156
|
+
const tree = result.patterns.find((p) => p.kind === "tree");
|
|
157
|
+
expect(tree).toBeDefined();
|
|
158
|
+
if (tree !== undefined) {
|
|
159
|
+
const rendered = renderPattern(tree);
|
|
160
|
+
expect(rendered).toMatch(/^r\.tree\(/);
|
|
161
|
+
expect(rendered).toContain("emit");
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
describe("Combined — feature with both r.treeActions and r.tree", () => {
|
|
167
|
+
test("both patterns coexist in the parse output, source-order preserved", () => {
|
|
168
|
+
const result = parseInline(`
|
|
169
|
+
import { defineFeature } from "@cosmicdrift/kumiko-framework/engine";
|
|
170
|
+
defineFeature("text-content", (r) => {
|
|
171
|
+
r.treeActions({ edit: { args: { slug: "" } } });
|
|
172
|
+
r.tree((ctx) => (emit) => { emit([{ label: "Marketing" }]); return () => {}; });
|
|
173
|
+
});
|
|
174
|
+
`);
|
|
175
|
+
|
|
176
|
+
expect(result.errors).toEqual([]);
|
|
177
|
+
const kinds = result.patterns.map((p) => p.kind);
|
|
178
|
+
const idxActions = kinds.indexOf("treeActions");
|
|
179
|
+
const idxTree = kinds.indexOf("tree");
|
|
180
|
+
expect(idxActions).toBeGreaterThanOrEqual(0);
|
|
181
|
+
expect(idxTree).toBeGreaterThanOrEqual(0);
|
|
182
|
+
expect(idxActions).toBeLessThan(idxTree);
|
|
183
|
+
});
|
|
184
|
+
});
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
export {
|
|
2
|
+
extractOptionalRequires,
|
|
3
|
+
extractReadsConfig,
|
|
4
|
+
extractRequires,
|
|
5
|
+
extractSystemScope,
|
|
6
|
+
extractToggleable,
|
|
7
|
+
} from "./round1";
|
|
8
|
+
export {
|
|
9
|
+
extractEntity,
|
|
10
|
+
extractNav,
|
|
11
|
+
extractRelation,
|
|
12
|
+
extractWorkspace,
|
|
13
|
+
} from "./round2";
|
|
14
|
+
export {
|
|
15
|
+
extractClaimKey,
|
|
16
|
+
extractConfig,
|
|
17
|
+
extractMetric,
|
|
18
|
+
extractReferenceData,
|
|
19
|
+
extractSecret,
|
|
20
|
+
extractTranslations,
|
|
21
|
+
extractUseExtension,
|
|
22
|
+
isClaimKeyType,
|
|
23
|
+
type NamedOptionsResult,
|
|
24
|
+
readNamedOptions,
|
|
25
|
+
} from "./round3";
|
|
26
|
+
export {
|
|
27
|
+
collectScreenOpaqueProps,
|
|
28
|
+
extractAuthClaims,
|
|
29
|
+
extractDefineEvent,
|
|
30
|
+
extractEntityHook,
|
|
31
|
+
extractEventMigration,
|
|
32
|
+
extractHook,
|
|
33
|
+
extractHttpRoute,
|
|
34
|
+
extractJob,
|
|
35
|
+
extractMultiStreamProjection,
|
|
36
|
+
extractNotification,
|
|
37
|
+
extractProjection,
|
|
38
|
+
extractQueryHandler,
|
|
39
|
+
extractScreen,
|
|
40
|
+
extractWriteHandler,
|
|
41
|
+
isEntityHookType,
|
|
42
|
+
isHookType,
|
|
43
|
+
isHttpRouteMethod,
|
|
44
|
+
type ParsedHandlerCall,
|
|
45
|
+
parseHandlerCall,
|
|
46
|
+
readApplyBodies,
|
|
47
|
+
readOptionalAccessRule,
|
|
48
|
+
readOptionalPhase,
|
|
49
|
+
readOptionalRateLimit,
|
|
50
|
+
readScreenStatic,
|
|
51
|
+
} from "./round4";
|
|
52
|
+
export {
|
|
53
|
+
extractExposesApi,
|
|
54
|
+
extractExtendsRegistrar,
|
|
55
|
+
extractUsesApi,
|
|
56
|
+
} from "./round5";
|
|
57
|
+
export {
|
|
58
|
+
extractTree,
|
|
59
|
+
extractTreeActions,
|
|
60
|
+
} from "./round6";
|
|
61
|
+
export type { ExtractOutput } from "./shared";
|
|
62
|
+
export {
|
|
63
|
+
fail,
|
|
64
|
+
findFunctionLiteral,
|
|
65
|
+
isPlainObject,
|
|
66
|
+
ok,
|
|
67
|
+
readBooleanProperty,
|
|
68
|
+
readDataLiteralNode,
|
|
69
|
+
readNameOrRef,
|
|
70
|
+
readNameOrRefOrList,
|
|
71
|
+
readPropertyKey,
|
|
72
|
+
readStringLiteralArgs,
|
|
73
|
+
readVarargsOrArrayProp,
|
|
74
|
+
} from "./shared";
|