@cosmicdrift/kumiko-framework 0.158.2 → 0.160.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 (168) hide show
  1. package/package.json +7 -2
  2. package/src/__tests__/consumer-cli.integration.test.ts +110 -0
  3. package/src/api/__tests__/api.test.ts +65 -0
  4. package/src/api/__tests__/auth-routes-cookie.test.ts +17 -1
  5. package/src/api/__tests__/auth-routes-invalid-body-invite.test.ts +237 -0
  6. package/src/api/__tests__/auth-routes-mfa-verify.test.ts +1 -0
  7. package/src/api/__tests__/csrf-constants-sync.test.ts +20 -0
  8. package/src/api/__tests__/dispatcher-live.integration.test.ts +74 -0
  9. package/src/api/__tests__/jwt.test.ts +150 -1
  10. package/src/api/__tests__/login-rate-limiter-sweep.test.ts +41 -0
  11. package/src/api/__tests__/server-boot-guards.test.ts +71 -0
  12. package/src/api/__tests__/server-jwt-ttl.test.ts +58 -0
  13. package/src/api/api-constants.ts +5 -0
  14. package/src/api/auth-middleware.ts +48 -59
  15. package/src/api/auth-routes.ts +51 -17
  16. package/src/api/index.ts +3 -3
  17. package/src/api/jwt.ts +148 -7
  18. package/src/api/pii-leak-guard.ts +5 -2
  19. package/src/api/routes.ts +57 -0
  20. package/src/api/server.ts +19 -5
  21. package/src/bun-db/__tests__/select-many-retry.test.ts +79 -0
  22. package/src/bun-db/query.ts +46 -27
  23. package/src/consumer-cli.ts +87 -0
  24. package/src/crypto/__tests__/pii-field-encryption.test.ts +69 -13
  25. package/src/crypto/blind-index.ts +8 -4
  26. package/src/crypto/event-pii.ts +1 -0
  27. package/src/crypto/kms-adapter.ts +2 -118
  28. package/src/crypto/pii-field-encryption.ts +49 -15
  29. package/src/db/__tests__/build-filter-where.test.ts +34 -0
  30. package/src/db/__tests__/event-store-executor-context.pii-roundtrip.test.ts +67 -0
  31. package/src/db/__tests__/event-store-executor-write-verbs.integration.test.ts +396 -0
  32. package/src/db/__tests__/event-store-executor.integration.test.ts +5 -5
  33. package/src/db/blind-index-cleanup.ts +3 -1
  34. package/src/db/connection.ts +3 -11
  35. package/src/db/cursor.ts +1 -18
  36. package/src/db/dialect.ts +8 -19
  37. package/src/db/encryption.ts +2 -3
  38. package/src/db/entity-table-meta-types.ts +2 -0
  39. package/src/db/entity-table-meta.ts +16 -90
  40. package/src/db/event-store-executor.ts +4 -96
  41. package/src/db/queries/backfill-pii.ts +1 -0
  42. package/src/db/queries/event-consumer.ts +35 -2
  43. package/src/db/table-builder.ts +2 -19
  44. package/src/db/tenant-db.ts +6 -55
  45. package/src/engine/__tests__/boot-validator-boot-check.test.ts +99 -0
  46. package/src/engine/__tests__/boot-validator-gdpr-storage.test.ts +7 -233
  47. package/src/engine/__tests__/boot-validator.test.ts +46 -0
  48. package/src/engine/__tests__/codemod-pipeline.test.ts +139 -10
  49. package/src/engine/__tests__/define-roles.test.ts +21 -0
  50. package/src/engine/__tests__/engine.test.ts +28 -0
  51. package/src/engine/__tests__/event-type-map-augmentation.test.ts +24 -0
  52. package/src/engine/__tests__/registry-facade-sweep.test.ts +80 -0
  53. package/src/engine/__tests__/registry.test.ts +40 -0
  54. package/src/engine/__tests__/store-table.test.ts +12 -0
  55. package/src/engine/__tests__/tier-resolver-extension.test.ts +19 -1
  56. package/src/engine/boot-validator/action-wiring.ts +1 -1
  57. package/src/engine/boot-validator/boot-check.ts +21 -0
  58. package/src/engine/boot-validator/entity-handler.ts +10 -1
  59. package/src/engine/boot-validator/entity-list-screens.ts +1 -1
  60. package/src/engine/boot-validator/gdpr-storage.ts +0 -112
  61. package/src/engine/boot-validator/index.ts +3 -9
  62. package/src/engine/boot-validator/screens.ts +1 -1
  63. package/src/engine/define-feature.ts +2 -0
  64. package/src/engine/define-handler.ts +11 -91
  65. package/src/engine/entity-handlers.ts +15 -27
  66. package/src/engine/feature-ast/__tests__/canonical-form.test.ts +11 -1
  67. package/src/engine/feature-ast/__tests__/parse.test.ts +983 -3
  68. package/src/engine/feature-ast/__tests__/patch.test.ts +168 -0
  69. package/src/engine/feature-ast/__tests__/patcher.test.ts +7 -0
  70. package/src/engine/feature-ast/__tests__/render-roundtrip.test.ts +9 -0
  71. package/src/engine/feature-ast/extractors/handlers.ts +19 -2
  72. package/src/engine/feature-ast/extractors/index.ts +1 -0
  73. package/src/engine/feature-ast/index.ts +2 -0
  74. package/src/engine/feature-ast/parse.ts +3 -0
  75. package/src/engine/feature-ast/patch.ts +2 -0
  76. package/src/engine/feature-ast/patcher.ts +21 -0
  77. package/src/engine/feature-ast/patterns.ts +16 -0
  78. package/src/engine/feature-ast/render.ts +15 -0
  79. package/src/engine/feature-builder-state.ts +6 -0
  80. package/src/engine/feature-config-events-jobs.ts +1 -1
  81. package/src/engine/feature-entity-handlers.ts +36 -2
  82. package/src/engine/feature-ui-extensions.ts +5 -1
  83. package/src/engine/field-helpers.ts +31 -0
  84. package/src/engine/handler-helpers.ts +26 -0
  85. package/src/engine/hook-helpers.ts +14 -0
  86. package/src/engine/index.ts +5 -2
  87. package/src/engine/ownership.ts +22 -76
  88. package/src/engine/pattern-library/__tests__/library.test.ts +9 -0
  89. package/src/engine/pattern-library/library.ts +2 -0
  90. package/src/engine/pattern-library/mixed-schemas.ts +37 -0
  91. package/src/engine/registry-facade.ts +9 -0
  92. package/src/engine/registry-ingest.ts +10 -0
  93. package/src/engine/registry-state.ts +3 -0
  94. package/src/engine/registry-validate.ts +1 -1
  95. package/src/engine/screen-helpers.ts +54 -0
  96. package/src/engine/tier-resolver-extension.ts +3 -2
  97. package/src/engine/types/config.ts +2 -497
  98. package/src/engine/types/define-handler.ts +2 -0
  99. package/src/engine/types/entity-handlers.ts +2 -0
  100. package/src/engine/types/event-type-map.ts +1 -37
  101. package/src/engine/types/feature.ts +2 -976
  102. package/src/engine/types/fields.ts +2 -697
  103. package/src/engine/types/handlers.ts +2 -839
  104. package/src/engine/types/hooks.ts +2 -184
  105. package/src/engine/types/http-route.ts +1 -72
  106. package/src/engine/types/identifiers.ts +1 -47
  107. package/src/engine/types/index.ts +66 -33
  108. package/src/engine/types/nav.ts +2 -67
  109. package/src/engine/types/ownership.ts +2 -0
  110. package/src/engine/types/projection.ts +2 -165
  111. package/src/engine/types/relations.ts +1 -51
  112. package/src/engine/types/screen.ts +2 -793
  113. package/src/engine/types/step.ts +2 -334
  114. package/src/engine/types/target-ref.ts +1 -21
  115. package/src/engine/types/tree-node.ts +1 -129
  116. package/src/engine/types/workspace.ts +2 -42
  117. package/src/entrypoint/index.ts +2 -2
  118. package/src/errors/write-error-info.ts +6 -22
  119. package/src/event-store/__tests__/event-store.integration.test.ts +31 -0
  120. package/src/event-store/__tests__/unscoped-stream-primitives.guard.test.ts +43 -0
  121. package/src/event-store/errors.ts +2 -35
  122. package/src/event-store/event-store.ts +28 -51
  123. package/src/event-store/events-schema.ts +1 -10
  124. package/src/event-store/index.ts +3 -2
  125. package/src/event-store/snapshot.ts +11 -35
  126. package/src/event-store/types.ts +2 -0
  127. package/src/files/__tests__/in-memory-provider.contract.test.ts +4 -0
  128. package/src/files/file-handle.ts +2 -19
  129. package/src/files/provider-resolver.ts +3 -5
  130. package/src/files/types.ts +5 -54
  131. package/src/i18n/required-surface-keys.ts +1 -1
  132. package/src/jobs/__tests__/jobs.integration.test.ts +102 -1
  133. package/src/logging/types.ts +1 -7
  134. package/src/observability/types/index.ts +1 -29
  135. package/src/observability/types/metric.ts +1 -56
  136. package/src/observability/types/provider.ts +1 -32
  137. package/src/observability/types/span.ts +1 -58
  138. package/src/pipeline/__tests__/dispatcher.test.ts +134 -1
  139. package/src/pipeline/__tests__/event-dispatcher-delivery-max-attempts.test.ts +126 -0
  140. package/src/pipeline/__tests__/event-dispatcher-rearm.integration.test.ts +180 -0
  141. package/src/pipeline/__tests__/lifecycle-pipeline.test.ts +208 -0
  142. package/src/pipeline/dispatch-shared.ts +51 -3
  143. package/src/pipeline/dispatch-stream.ts +74 -0
  144. package/src/pipeline/dispatcher-utils.ts +1 -1
  145. package/src/pipeline/dispatcher.ts +7 -0
  146. package/src/pipeline/entity-cache.ts +2 -33
  147. package/src/pipeline/event-consumer-state.ts +28 -3
  148. package/src/pipeline/event-dispatcher-admin.ts +4 -0
  149. package/src/pipeline/event-dispatcher-delivery.ts +29 -3
  150. package/src/pipeline/event-dispatcher.ts +27 -1
  151. package/src/pipeline/multi-stream-apply-context.ts +4 -42
  152. package/src/pipeline/system-hooks.ts +7 -0
  153. package/src/rate-limit/resolver.ts +10 -30
  154. package/src/search/types.ts +1 -39
  155. package/src/secrets/__tests__/envelope-cipher.test.ts +2 -30
  156. package/src/secrets/__tests__/envelope.test.ts +1 -1
  157. package/src/secrets/envelope-cipher.ts +17 -45
  158. package/src/secrets/types.ts +2 -177
  159. package/src/stack/__tests__/event-collector.test.ts +42 -0
  160. package/src/testing/__tests__/late-bound.test.ts +25 -0
  161. package/src/testing/__tests__/wait-for.test.ts +53 -0
  162. package/src/testing/boot-validator-fixture.ts +1 -1
  163. package/src/testing/file-provider-contract.ts +84 -0
  164. package/src/testing/handler-context.ts +1 -1
  165. package/src/testing/index.ts +1 -0
  166. package/src/time/geo-tz.ts +1 -32
  167. package/src/time/tz-context.ts +9 -56
  168. package/src/ui-types/index.ts +7 -7
@@ -0,0 +1,84 @@
1
+ import { beforeEach, describe, expect, test } from "bun:test";
2
+ import type { FileStorageProvider } from "../files/types";
3
+
4
+ const bytes = (s: string) => new TextEncoder().encode(s);
5
+ const decode = (u: Uint8Array) => new TextDecoder().decode(u);
6
+
7
+ async function* fromChunks(chunks: Uint8Array[]): AsyncIterable<Uint8Array> {
8
+ for (const c of chunks) yield c;
9
+ }
10
+
11
+ // Mirrors describeKmsAdapterContract (crypto/__tests__/kms-adapter-contract.ts):
12
+ // pins the FileStorageProvider contract against whatever implementation the
13
+ // factory returns. Provider-specific behavior (error messages, buffer
14
+ // copies, multipart details) stays in each provider's dedicated tests.
15
+ export function describeFileProviderContract(
16
+ name: string,
17
+ factory: () => FileStorageProvider | Promise<FileStorageProvider>,
18
+ ): void {
19
+ describe(`${name} — FileStorageProvider contract`, () => {
20
+ let provider: FileStorageProvider;
21
+
22
+ beforeEach(async () => {
23
+ provider = await factory();
24
+ });
25
+
26
+ test("write + read roundtrip preserves bytes", async () => {
27
+ const key = `contract/${crypto.randomUUID()}.bin`;
28
+ await provider.write(key, bytes("hello contract"));
29
+ expect(decode(await provider.read(key))).toBe("hello contract");
30
+ });
31
+
32
+ test("write on an existing key overwrites it (last-write-wins)", async () => {
33
+ const key = `contract/${crypto.randomUUID()}.bin`;
34
+ await provider.write(key, bytes("first"));
35
+ await provider.write(key, bytes("second"));
36
+ expect(decode(await provider.read(key))).toBe("second");
37
+ });
38
+
39
+ test("read throws for a missing key", async () => {
40
+ await expect(provider.read(`contract/missing-${crypto.randomUUID()}`)).rejects.toThrow();
41
+ });
42
+
43
+ test("exists reflects write + delete", async () => {
44
+ const key = `contract/${crypto.randomUUID()}.txt`;
45
+ expect(await provider.exists(key)).toBe(false);
46
+ await provider.write(key, bytes("x"));
47
+ expect(await provider.exists(key)).toBe(true);
48
+ await provider.delete(key);
49
+ expect(await provider.exists(key)).toBe(false);
50
+ });
51
+
52
+ test("delete on a missing key is a no-op", async () => {
53
+ await expect(
54
+ provider.delete(`contract/never-existed-${crypto.randomUUID()}`),
55
+ ).resolves.toBeUndefined();
56
+ });
57
+
58
+ test("writeStream + readStream roundtrip preserves bytes", async () => {
59
+ const key = `contract/${crypto.randomUUID()}.stream`;
60
+ await provider.writeStream(key, fromChunks([bytes("foo"), bytes("bar")]));
61
+ let out = "";
62
+ for await (const chunk of provider.readStream(key)) out += decode(chunk);
63
+ expect(out).toBe("foobar");
64
+ });
65
+
66
+ test("readStream on a missing key throws on the first chunk pull", async () => {
67
+ const it = provider
68
+ .readStream(`contract/missing-${crypto.randomUUID()}`)
69
+ [Symbol.asyncIterator]();
70
+ await expect(it.next()).rejects.toThrow();
71
+ });
72
+
73
+ test("getSignedUrl, when implemented, returns a URL string", async () => {
74
+ // skip: getSignedUrl is optional on the contract — feature-detected
75
+ if (!provider.getSignedUrl) return;
76
+
77
+ const key = `contract/${crypto.randomUUID()}.txt`;
78
+ await provider.write(key, bytes("signed"));
79
+ const url = await provider.getSignedUrl(key, 60);
80
+ expect(typeof url).toBe("string");
81
+ expect(url.length).toBeGreaterThan(0);
82
+ });
83
+ });
84
+ }
@@ -117,7 +117,7 @@ export function bridgeStub(opts?: {
117
117
  ) as unknown as HandlerContext["resolveAuthClaims"],
118
118
  // Stub defaults to always-enabled — matches the dispatcher's behaviour
119
119
  // when no effectiveFeatures resolver is wired (tests without toggles).
120
- hasFeature: () => true,
120
+ hasFeature: async () => true,
121
121
  metrics: createNoopMetricsHandle(),
122
122
  tracer: noopTracer,
123
123
  // Echter TzContext, kein notAvailable — Test-Code nutzt ctx.tz häufig
@@ -15,6 +15,7 @@ export {
15
15
  generateZodFixture,
16
16
  } from "./e2e-generator";
17
17
  export { expectErrorIncludes } from "./expect-error";
18
+ export { describeFileProviderContract } from "./file-provider-contract";
18
19
  export { bridgeStub } from "./handler-context";
19
20
  export {
20
21
  getSetCookieRaw,
@@ -1,32 +1 @@
1
- // GeoTzProvider optionaler Adapter Geo-Position → IANA-Zone. Das Framework
2
- // definiert NUR das Interface + den Injection-Seam (ctx.tz.fromCoordinates /
3
- // fromAddress); eine konkrete Implementation kommt aus einem separaten Paket
4
- // (z.B. ein geo-tz-basiertes Offline-Paket). v1-Default: kein Provider — die
5
- // fromCoordinates/fromAddress-Methoden werfen klar statt still falsch zu raten.
6
- //
7
- // `fromCoordinates` ist die PRIMÄRE Methode: Offline-geo-tz-Libs lösen
8
- // lat/lng → Zone (genau, offline, kostenlos) — sie kennen keine Postadressen.
9
- // `fromAddress` ist optional, für Provider die eine Geocoding-API (Adresse →
10
- // Zone, online) anbinden. Diese Trennung hält das Interface kompatibel mit
11
- // beiden Provider-Klassen, statt eine Adress-Form zu erzwingen die der
12
- // Offline-Fall gar nicht bedienen kann.
13
-
14
- export type GeoCoordinates = {
15
- readonly latitude: number;
16
- readonly longitude: number;
17
- };
18
-
19
- export type GeoAddress = {
20
- readonly street?: string;
21
- readonly city?: string;
22
- readonly region?: string;
23
- readonly postalCode?: string;
24
- readonly country?: string;
25
- };
26
-
27
- export type GeoTzProvider = {
28
- /** Geo-Koordinaten → IANA-Zone (offline lat/lng → tz). */
29
- readonly fromCoordinates: (coords: GeoCoordinates) => string | Promise<string>;
30
- /** Optional: Postadresse → IANA-Zone (Geocoding-API-Provider). */
31
- readonly fromAddress?: (address: GeoAddress) => string | Promise<string>;
32
- };
1
+ export * from "@cosmicdrift/kumiko-types/geo-tz";
@@ -13,65 +13,18 @@
13
13
  // `tenant` + `user` sind die TZ-Defaults für den aktuellen Request. Aktueller
14
14
  // Stand: beide default auf "UTC" — sobald tenant.timezone +
15
15
  // user.timezone Felder existieren, lese ich sie aus dem Request-Context.
16
+ //
17
+ // Die reinen Type-Contracts (TzContext, TzContextOptions, LocatedTimestampJson)
18
+ // leben in @cosmicdrift/kumiko-types/tz-context — hier nur die Factories.
16
19
 
17
- import type { GeoAddress, GeoCoordinates, GeoTzProvider } from "./geo-tz";
20
+ import type { TzContext, TzContextOptions } from "@cosmicdrift/kumiko-types/tz-context";
18
21
  import { ensureTemporalPolyfill, getTemporal } from "./polyfill";
19
22
 
20
- // JSON-Form für Wall-Clock+TZ — siehe createLocatedTimestampField() in
21
- // engine/factories.ts. Zwei Felder, idiotensicher.
22
- export type LocatedTimestampJson = {
23
- /** Wall-Clock-ISO ohne Offset, z.B. "2026-04-03T10:00:00" */
24
- readonly at: string;
25
- /** IANA-Zone, z.B. "Europe/Lisbon" */
26
- readonly tz: string;
27
- };
28
-
29
- export type TzContext = {
30
- /** Default-TZ des Mandanten (aus tenant.timezone, default "UTC"). */
31
- readonly tenant: string;
32
- /** Anzeige-TZ des aktuellen Users (User-Profil-Override, fallback Tenant). */
33
- readonly user: string;
34
-
35
- /** Aktueller Moment als UTC-Instant. */
36
- now(): Temporal.Instant;
37
- /** Aktueller Moment als ZonedDateTime in der gewünschten Zone. */
38
- nowIn(tz: string): Temporal.ZonedDateTime;
39
-
40
- /** Heutiges Kalender-Datum in der gewünschten Zone. */
41
- today(tz: string): Temporal.PlainDate;
42
- /** Tagesgrenzen (00:00 bis 24:00 nächster Tag) als UTC-Instants — für DB-Range-Queries. */
43
- todayRange(tz: string): { readonly start: Temporal.Instant; readonly end: Temporal.Instant };
44
-
45
- /** Wall-Clock-String + IANA-Zone → ZonedDateTime. */
46
- parse(wallClock: string, tz: string): Temporal.ZonedDateTime;
47
-
48
- /** ZonedDateTime → UTC-Instant. */
49
- toInstant(zdt: Temporal.ZonedDateTime): Temporal.Instant;
50
-
51
- /** ZonedDateTime → JSON-Pair { at, tz } (API-Boundary). */
52
- toLocatedJson(zdt: Temporal.ZonedDateTime): LocatedTimestampJson;
53
-
54
- /** JSON-Pair { at, tz } → ZonedDateTime (Wall-Clock + IANA). */
55
- fromLocatedJson(obj: LocatedTimestampJson): Temporal.ZonedDateTime;
56
-
57
- /** Geo-Koordinaten → IANA-Zone via konfiguriertem GeoTzProvider.
58
- * Wirft wenn kein Provider konfiguriert ist (v1-Default). */
59
- fromCoordinates(coords: GeoCoordinates): Promise<string>;
60
- /** Postadresse → IANA-Zone via GeoTzProvider. Wirft wenn kein Provider
61
- * konfiguriert ist ODER der Provider kein fromAddress unterstützt (der
62
- * Offline-lat/lng-Provider tut das nicht). */
63
- fromAddress(address: GeoAddress): Promise<string>;
64
- };
65
-
66
- export type TzContextOptions = {
67
- /** Tenant-Default-TZ. Default "UTC" wenn nicht gesetzt. */
68
- readonly tenant?: string;
69
- /** User-Override. Default = tenant. */
70
- readonly user?: string;
71
- /** Optionaler Geo→Zone-Adapter für ctx.tz.fromCoordinates / fromAddress.
72
- * Ohne Provider werfen diese Methoden. */
73
- readonly geoTz?: GeoTzProvider;
74
- };
23
+ export type {
24
+ LocatedTimestampJson,
25
+ TzContext,
26
+ TzContextOptions,
27
+ } from "@cosmicdrift/kumiko-types/tz-context";
75
28
 
76
29
  /**
77
30
  * Factory: erzeugt einen TzContext für den aktuellen Request.
@@ -22,6 +22,13 @@
22
22
 
23
23
  export type { ParsedRefTarget } from "../engine/parse-ref-target";
24
24
  export { parseRefTarget } from "../engine/parse-ref-target";
25
+ export {
26
+ evalFieldCondition,
27
+ isExtensionEditSection,
28
+ isFormatSpec,
29
+ normalizeEditField,
30
+ normalizeListColumn,
31
+ } from "../engine/screen-helpers";
25
32
  // Entity + field types. EntityDefinition is the canonical shape that
26
33
  // view-model builders iterate; FieldDefinition is the per-field union
27
34
  // (text, number, boolean, ...) they branch on. AccessRule is used by
@@ -79,13 +86,6 @@ export type {
79
86
  ScreenSlots,
80
87
  ToolbarAction,
81
88
  } from "../engine/types/screen";
82
- export {
83
- evalFieldCondition,
84
- isExtensionEditSection,
85
- isFormatSpec,
86
- normalizeEditField,
87
- normalizeListColumn,
88
- } from "../engine/types/screen";
89
89
  export type { TargetRef } from "../engine/types/target-ref";
90
90
  export type { TreeAction, TreeNode, TreeNodeState } from "../engine/types/tree-node";
91
91
  export type { WorkspaceDefinition } from "../engine/types/workspace";