@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.
Files changed (191) hide show
  1. package/CHANGELOG.md +54 -0
  2. package/package.json +124 -38
  3. package/src/__tests__/full-stack.integration.ts +2 -2
  4. package/src/api/auth-routes.ts +5 -5
  5. package/src/api/jwt.ts +2 -2
  6. package/src/api/route-registrars.ts +1 -1
  7. package/src/api/routes.ts +3 -3
  8. package/src/api/server.ts +6 -7
  9. package/src/auth/__tests__/roles.test.ts +24 -0
  10. package/src/auth/index.ts +7 -0
  11. package/src/auth/roles.ts +42 -0
  12. package/src/compliance/__tests__/duration-spec.test.ts +72 -0
  13. package/src/compliance/__tests__/profiles.test.ts +308 -0
  14. package/src/compliance/__tests__/sub-processors.test.ts +139 -0
  15. package/src/compliance/duration-spec.ts +44 -0
  16. package/src/compliance/index.ts +31 -0
  17. package/src/compliance/override-schema.ts +136 -0
  18. package/src/compliance/profiles.ts +427 -0
  19. package/src/compliance/sub-processors.ts +152 -0
  20. package/src/db/__tests__/big-int-field.test.ts +131 -0
  21. package/src/db/assert-exists-in.ts +2 -2
  22. package/src/db/cursor.ts +3 -3
  23. package/src/db/event-store-executor.ts +19 -13
  24. package/src/db/located-timestamp.ts +1 -1
  25. package/src/db/money.ts +12 -2
  26. package/src/db/pg-error.ts +1 -1
  27. package/src/db/row-helpers.ts +1 -1
  28. package/src/db/table-builder.ts +20 -5
  29. package/src/db/tenant-db.ts +9 -9
  30. package/src/engine/__tests__/_pipeline-test-utils.ts +23 -0
  31. package/src/engine/__tests__/boot-validator-api-exposure.test.ts +142 -0
  32. package/src/engine/__tests__/boot-validator-pii-retention.test.ts +570 -0
  33. package/src/engine/__tests__/boot-validator-s0-integration.test.ts +160 -0
  34. package/src/engine/__tests__/build-target.test.ts +135 -0
  35. package/src/engine/__tests__/codemod-pipeline.test.ts +551 -0
  36. package/src/engine/__tests__/entity-handlers.test.ts +3 -3
  37. package/src/engine/__tests__/event-helpers.test.ts +4 -4
  38. package/src/engine/__tests__/pipeline-engine.test.ts +215 -0
  39. package/src/engine/__tests__/pipeline-handler.integration.ts +894 -0
  40. package/src/engine/__tests__/pipeline-observability.integration.ts +142 -0
  41. package/src/engine/__tests__/pipeline-performance.integration.ts +152 -0
  42. package/src/engine/__tests__/pipeline-sub-pipelines.test.ts +288 -0
  43. package/src/engine/__tests__/raw-table.test.ts +2 -2
  44. package/src/engine/__tests__/steps-aggregate-append-event.test.ts +115 -0
  45. package/src/engine/__tests__/steps-aggregate-create.test.ts +92 -0
  46. package/src/engine/__tests__/steps-aggregate-update.test.ts +127 -0
  47. package/src/engine/__tests__/steps-call-feature.test.ts +123 -0
  48. package/src/engine/__tests__/steps-mail-send.test.ts +136 -0
  49. package/src/engine/__tests__/steps-read.test.ts +142 -0
  50. package/src/engine/__tests__/steps-resolver-utils.test.ts +50 -0
  51. package/src/engine/__tests__/steps-unsafe-projection-delete.test.ts +69 -0
  52. package/src/engine/__tests__/steps-unsafe-projection-upsert.test.ts +117 -0
  53. package/src/engine/__tests__/steps-webhook-send.test.ts +135 -0
  54. package/src/engine/__tests__/steps-workflow.test.ts +198 -0
  55. package/src/engine/__tests__/validate-projection-allowlist.test.ts +491 -0
  56. package/src/engine/__tests__/visual-tree-patterns.test.ts +251 -0
  57. package/src/engine/boot-validator/api-ext.ts +77 -0
  58. package/src/engine/boot-validator/config-deps.ts +163 -0
  59. package/src/engine/boot-validator/entity-handler.ts +466 -0
  60. package/src/engine/boot-validator/index.ts +159 -0
  61. package/src/engine/boot-validator/ownership.ts +198 -0
  62. package/src/engine/boot-validator/pii-retention.ts +155 -0
  63. package/src/engine/boot-validator/screens-nav.ts +624 -0
  64. package/src/engine/boot-validator.ts +1 -1528
  65. package/src/engine/build-app-schema.ts +1 -1
  66. package/src/engine/build-target.ts +99 -0
  67. package/src/engine/codemod/index.ts +15 -0
  68. package/src/engine/codemod/pipeline-codemod.ts +641 -0
  69. package/src/engine/config-helpers.ts +9 -19
  70. package/src/engine/constants.ts +1 -1
  71. package/src/engine/define-feature.ts +127 -9
  72. package/src/engine/define-handler.ts +89 -3
  73. package/src/engine/define-roles.ts +2 -2
  74. package/src/engine/define-step.ts +28 -0
  75. package/src/engine/define-workflow.ts +110 -0
  76. package/src/engine/entity-handlers.ts +10 -9
  77. package/src/engine/event-helpers.ts +4 -4
  78. package/src/engine/extension-names.ts +105 -0
  79. package/src/engine/extensions/user-data.ts +106 -0
  80. package/src/engine/factories.ts +26 -16
  81. package/src/engine/feature-ast/__tests__/visual-tree-parse.test.ts +184 -0
  82. package/src/engine/feature-ast/extractors/index.ts +74 -0
  83. package/src/engine/feature-ast/extractors/round1.ts +110 -0
  84. package/src/engine/feature-ast/extractors/round2.ts +253 -0
  85. package/src/engine/feature-ast/extractors/round3.ts +471 -0
  86. package/src/engine/feature-ast/extractors/round4.ts +1365 -0
  87. package/src/engine/feature-ast/extractors/round5.ts +72 -0
  88. package/src/engine/feature-ast/extractors/round6.ts +66 -0
  89. package/src/engine/feature-ast/extractors/shared.ts +177 -0
  90. package/src/engine/feature-ast/parse.ts +13 -0
  91. package/src/engine/feature-ast/patch.ts +9 -1
  92. package/src/engine/feature-ast/patcher.ts +10 -3
  93. package/src/engine/feature-ast/patterns.ts +71 -1
  94. package/src/engine/feature-ast/render.ts +31 -1
  95. package/src/engine/index.ts +66 -2
  96. package/src/engine/pattern-library/__tests__/library.test.ts +11 -0
  97. package/src/engine/pattern-library/library.ts +78 -2
  98. package/src/engine/pipeline.ts +88 -0
  99. package/src/engine/projection-helpers.ts +1 -1
  100. package/src/engine/read-claim.ts +1 -1
  101. package/src/engine/registry.ts +30 -2
  102. package/src/engine/resolve-config-or-param.ts +4 -0
  103. package/src/engine/run-pipeline.ts +162 -0
  104. package/src/engine/schema-builder.ts +10 -4
  105. package/src/engine/state-machine.ts +1 -1
  106. package/src/engine/steps/_drizzle-boundary.ts +19 -0
  107. package/src/engine/steps/_duration-utils.ts +33 -0
  108. package/src/engine/steps/_no-return-guard.ts +21 -0
  109. package/src/engine/steps/_resolver-utils.ts +42 -0
  110. package/src/engine/steps/_step-dispatch-constants.ts +38 -0
  111. package/src/engine/steps/aggregate-append-event.ts +56 -0
  112. package/src/engine/steps/aggregate-create.ts +56 -0
  113. package/src/engine/steps/aggregate-update.ts +68 -0
  114. package/src/engine/steps/branch.ts +84 -0
  115. package/src/engine/steps/call-feature.ts +49 -0
  116. package/src/engine/steps/compute.ts +41 -0
  117. package/src/engine/steps/for-each.ts +111 -0
  118. package/src/engine/steps/mail-send.ts +44 -0
  119. package/src/engine/steps/read-find-many.ts +51 -0
  120. package/src/engine/steps/read-find-one.ts +58 -0
  121. package/src/engine/steps/retry.ts +87 -0
  122. package/src/engine/steps/return.ts +34 -0
  123. package/src/engine/steps/unsafe-projection-delete.ts +46 -0
  124. package/src/engine/steps/unsafe-projection-upsert.ts +69 -0
  125. package/src/engine/steps/wait-for-event.ts +71 -0
  126. package/src/engine/steps/wait.ts +69 -0
  127. package/src/engine/steps/webhook-send.ts +71 -0
  128. package/src/engine/system-user.ts +1 -1
  129. package/src/engine/types/feature.ts +143 -1
  130. package/src/engine/types/fields.ts +134 -10
  131. package/src/engine/types/handlers.ts +18 -10
  132. package/src/engine/types/identifiers.ts +1 -0
  133. package/src/engine/types/index.ts +15 -1
  134. package/src/engine/types/step.ts +334 -0
  135. package/src/engine/types/target-ref.ts +21 -0
  136. package/src/engine/types/tree-node.ts +130 -0
  137. package/src/engine/types/workspace.ts +7 -0
  138. package/src/engine/validate-projection-allowlist.ts +161 -0
  139. package/src/event-store/snapshot.ts +1 -1
  140. package/src/event-store/upcaster-dead-letter.ts +1 -1
  141. package/src/event-store/upcaster.ts +1 -1
  142. package/src/files/__tests__/read-stream.test.ts +105 -0
  143. package/src/files/__tests__/write-stream.test.ts +233 -0
  144. package/src/files/__tests__/zip-stream.test.ts +357 -0
  145. package/src/files/file-routes.ts +1 -1
  146. package/src/files/in-memory-provider.ts +38 -0
  147. package/src/files/index.ts +3 -0
  148. package/src/files/local-provider.ts +58 -1
  149. package/src/files/types.ts +36 -8
  150. package/src/files/zip-stream.ts +251 -0
  151. package/src/jobs/job-runner.ts +10 -10
  152. package/src/lifecycle/lifecycle.ts +0 -3
  153. package/src/logging/index.ts +1 -0
  154. package/src/logging/pino-logger.ts +11 -7
  155. package/src/logging/utils.ts +24 -0
  156. package/src/observability/prometheus-meter.ts +7 -5
  157. package/src/pipeline/__tests__/archive-stream.integration.ts +1 -1
  158. package/src/pipeline/__tests__/causation-chain.integration.ts +1 -1
  159. package/src/pipeline/__tests__/domain-events-projections.integration.ts +3 -3
  160. package/src/pipeline/__tests__/event-define-event-strict.integration.ts +4 -4
  161. package/src/pipeline/__tests__/load-aggregate-query.integration.ts +1 -1
  162. package/src/pipeline/__tests__/msp-multi-hop.integration.ts +3 -3
  163. package/src/pipeline/__tests__/msp-rebuild.integration.ts +3 -3
  164. package/src/pipeline/__tests__/multi-stream-projection.integration.ts +2 -2
  165. package/src/pipeline/__tests__/query-projection.integration.ts +5 -5
  166. package/src/pipeline/append-event-core.ts +22 -6
  167. package/src/pipeline/dispatcher-utils.ts +188 -0
  168. package/src/pipeline/dispatcher.ts +63 -283
  169. package/src/pipeline/distributed-lock.ts +1 -1
  170. package/src/pipeline/entity-cache.ts +2 -2
  171. package/src/pipeline/event-consumer-state.ts +0 -13
  172. package/src/pipeline/event-dispatcher.ts +4 -4
  173. package/src/pipeline/index.ts +0 -2
  174. package/src/pipeline/lifecycle-pipeline.ts +6 -12
  175. package/src/pipeline/msp-rebuild.ts +5 -5
  176. package/src/pipeline/multi-stream-apply-context.ts +6 -7
  177. package/src/pipeline/projection-rebuild.ts +2 -2
  178. package/src/pipeline/projection-state.ts +0 -12
  179. package/src/rate-limit/__tests__/resolver.integration.ts +8 -4
  180. package/src/rate-limit/resolver.ts +1 -1
  181. package/src/search/in-memory-adapter.ts +1 -1
  182. package/src/search/meilisearch-adapter.ts +3 -3
  183. package/src/search/types.ts +1 -1
  184. package/src/secrets/leak-guard.ts +2 -2
  185. package/src/stack/request-helper.ts +9 -5
  186. package/src/stack/test-stack.ts +1 -1
  187. package/src/testing/handler-context.ts +4 -4
  188. package/src/testing/http-cookies.ts +1 -1
  189. package/src/time/tz-context.ts +1 -2
  190. package/src/ui-types/index.ts +4 -0
  191. 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
+ }
@@ -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";