@cosmicdrift/kumiko-framework 0.165.0 → 2.0.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 (146) hide show
  1. package/package.json +5 -3
  2. package/src/__tests__/consumer-cli.integration.test.ts +32 -0
  3. package/src/__tests__/schema-cli.integration.test.ts +1 -1
  4. package/src/api/__tests__/api.test.ts +267 -19
  5. package/src/api/__tests__/auth-middleware-anonymous-access-boot.test.ts +40 -0
  6. package/src/api/__tests__/auth-routes-invalid-body-invite.test.ts +16 -0
  7. package/src/api/__tests__/auth-routes-mfa-preauth-confirm.test.ts +2 -1
  8. package/src/api/__tests__/auth-routes-mfa-preauth-enable-start.test.ts +64 -1
  9. package/src/api/__tests__/auth-routes-trusted-proxy.test.ts +135 -0
  10. package/src/api/__tests__/batch.integration.test.ts +21 -2
  11. package/src/api/__tests__/jwt.test.ts +52 -2
  12. package/src/api/__tests__/redis-login-rate-limiter.integration.test.ts +72 -0
  13. package/src/api/__tests__/sse-broker.test.ts +57 -0
  14. package/src/api/__tests__/sse-route.test.ts +4 -0
  15. package/src/api/auth-routes.ts +165 -33
  16. package/src/api/index.ts +1 -0
  17. package/src/api/jwt.ts +22 -1
  18. package/src/api/routes.ts +103 -35
  19. package/src/api/server.ts +17 -1
  20. package/src/api/sse-broker.ts +39 -0
  21. package/src/bun-db/index.ts +1 -0
  22. package/src/bun-db/query.ts +12 -3
  23. package/src/consumer-cli.ts +60 -13
  24. package/src/db/__tests__/event-store-executor-write-verbs.integration.test.ts +14 -1
  25. package/src/db/__tests__/located-timestamp.test.ts +19 -0
  26. package/src/db/__tests__/migrate-runner.test.ts +61 -0
  27. package/src/db/__tests__/replay-migration-sql.test.ts +131 -2
  28. package/src/db/__tests__/tenant-db-where-merge.test.ts +6 -2
  29. package/src/db/api.ts +2 -2
  30. package/src/db/bun-provider.ts +2 -2
  31. package/src/db/connection.ts +6 -3
  32. package/src/db/dialect.ts +1 -6
  33. package/src/db/entity-table-meta-types.ts +1 -1
  34. package/src/db/event-store-executor-context.ts +2 -3
  35. package/src/db/event-store-executor-read.ts +2 -3
  36. package/src/db/event-store-executor-write.ts +8 -0
  37. package/src/db/index.ts +8 -1
  38. package/src/db/located-timestamp.ts +4 -0
  39. package/src/db/migrate-runner.ts +107 -11
  40. package/src/db/pg-error.ts +8 -0
  41. package/src/db/postgres-provider.ts +2 -2
  42. package/src/db/queries/__tests__/event-store-idempotency-index.integration.test.ts +80 -0
  43. package/src/db/queries/ddl.ts +45 -0
  44. package/src/db/queries/event-store.ts +97 -5
  45. package/src/db/queries/test-stack.ts +4 -30
  46. package/src/db/reference-data.ts +2 -3
  47. package/src/db/replay-migration-sql.ts +114 -12
  48. package/src/db/tenant-db.ts +2 -4
  49. package/src/engine/__tests__/engine.test.ts +30 -0
  50. package/src/engine/__tests__/schema-builder.test.ts +18 -0
  51. package/src/engine/__tests__/store-table.test.ts +2 -2
  52. package/src/engine/boot-validator/nav.ts +5 -0
  53. package/src/engine/constants.ts +32 -6
  54. package/src/engine/create-app.ts +11 -0
  55. package/src/engine/effective-features.ts +12 -2
  56. package/src/engine/extensions/user-data.ts +12 -4
  57. package/src/engine/feature-ui-extensions.ts +2 -2
  58. package/src/engine/hook-helpers.ts +3 -1
  59. package/src/engine/index.ts +1 -1
  60. package/src/engine/ownership.ts +4 -3
  61. package/src/engine/registry-ingest.ts +14 -14
  62. package/src/engine/registry-state.ts +4 -1
  63. package/src/engine/schema-builder.ts +1 -0
  64. package/src/engine/steps/__tests__/duration-utils.test.ts +20 -0
  65. package/src/engine/steps/_duration-utils.ts +2 -0
  66. package/src/engine/steps/unsafe-projection-upsert.ts +1 -4
  67. package/src/engine/types/config.ts +1 -1
  68. package/src/engine/types/define-handler.ts +1 -1
  69. package/src/engine/types/entity-handlers.ts +1 -1
  70. package/src/engine/types/event-type-map.ts +1 -1
  71. package/src/engine/types/feature.ts +1 -1
  72. package/src/engine/types/fields.ts +1 -1
  73. package/src/engine/types/handlers.ts +1 -1
  74. package/src/engine/types/hooks.ts +1 -1
  75. package/src/engine/types/http-route.ts +1 -1
  76. package/src/engine/types/nav.ts +1 -1
  77. package/src/engine/types/ownership.ts +1 -1
  78. package/src/engine/types/projection.ts +1 -1
  79. package/src/engine/types/relations.ts +1 -1
  80. package/src/engine/types/screen.ts +1 -1
  81. package/src/engine/types/step.ts +1 -1
  82. package/src/engine/types/target-ref.ts +1 -1
  83. package/src/engine/types/tree-node.ts +1 -1
  84. package/src/engine/types/workspace.ts +1 -1
  85. package/src/engine/validate-projection-allowlist.ts +5 -5
  86. package/src/errors/classes.ts +21 -0
  87. package/src/errors/index.ts +1 -0
  88. package/src/errors/write-error-info.ts +6 -2
  89. package/src/event-store/__tests__/admin-api.integration.test.ts +27 -1
  90. package/src/event-store/__tests__/event-store.integration.test.ts +32 -0
  91. package/src/event-store/__tests__/unscoped-stream-primitives.guard.test.ts +22 -4
  92. package/src/event-store/admin-api.ts +11 -4
  93. package/src/event-store/event-store.ts +19 -4
  94. package/src/event-store/types.ts +1 -1
  95. package/src/files/__tests__/build-storage-key.test.ts +28 -0
  96. package/src/files/__tests__/local-provider.test.ts +31 -0
  97. package/src/files/__tests__/write-stream.test.ts +3 -3
  98. package/src/files/index.ts +1 -1
  99. package/src/files/local-provider.ts +6 -1
  100. package/src/files/types.ts +8 -1
  101. package/src/jobs/__tests__/jobs.integration.test.ts +167 -7
  102. package/src/jobs/job-runner.ts +41 -11
  103. package/src/logging/types.ts +1 -1
  104. package/src/observability/index.ts +1 -0
  105. package/src/observability/standard-metrics.ts +35 -2
  106. package/src/observability/types/index.ts +1 -1
  107. package/src/observability/types/metric.ts +1 -1
  108. package/src/observability/types/provider.ts +1 -1
  109. package/src/observability/types/span.ts +1 -1
  110. package/src/pipeline/__tests__/dispatcher.test.ts +151 -0
  111. package/src/pipeline/__tests__/event-consumer-state.integration.test.ts +31 -0
  112. package/src/pipeline/__tests__/event-dispatcher-rearm.integration.test.ts +83 -0
  113. package/src/pipeline/__tests__/lifecycle-pipeline.test.ts +107 -97
  114. package/src/pipeline/dispatch-shared.ts +59 -6
  115. package/src/pipeline/dispatch-stream.ts +32 -11
  116. package/src/pipeline/dispatcher.ts +7 -1
  117. package/src/pipeline/event-consumer-state.ts +16 -13
  118. package/src/pipeline/event-dispatcher-delivery.ts +20 -6
  119. package/src/pipeline/event-dispatcher.ts +22 -0
  120. package/src/pipeline/index.ts +2 -0
  121. package/src/pipeline/system-hooks.ts +87 -0
  122. package/src/rate-limit/__tests__/resolver.integration.test.ts +18 -0
  123. package/src/rate-limit/resolver.ts +6 -2
  124. package/src/schema-cli.ts +24 -12
  125. package/src/search/__tests__/reindex-entity.integration.test.ts +24 -1
  126. package/src/search/reindex-entity.ts +31 -2
  127. package/src/search/types.ts +1 -1
  128. package/src/stack/__tests__/setup-test-stack-jobs.integration.test.ts +6 -2
  129. package/src/stack/db.ts +2 -1
  130. package/src/stack/push-entity-projection-tables.ts +2 -1
  131. package/src/stack/request-helper.ts +20 -1
  132. package/src/stack/table-helpers.ts +6 -4
  133. package/src/stack/test-stack.ts +18 -15
  134. package/src/testing/__tests__/late-bound.test.ts +7 -0
  135. package/src/testing/__tests__/wait-for.test.ts +6 -0
  136. package/src/testing/file-provider-contract.ts +26 -6
  137. package/src/testing/index.ts +1 -0
  138. package/src/testing/late-bound.ts +5 -3
  139. package/src/testing/wait-for.ts +3 -0
  140. package/src/testing/without-ambient-temporal.ts +14 -0
  141. package/src/time/geo-tz.ts +1 -1
  142. package/src/time/polyfill.ts +21 -38
  143. package/src/time/tz-context.ts +37 -31
  144. package/src/utils/__tests__/safe-json-temporal.test.ts +18 -0
  145. package/src/utils/safe-json.ts +13 -1
  146. package/src/engine/__tests__/registry-facade-sweep.test.ts +0 -80
@@ -14,6 +14,9 @@ export async function waitFor(
14
14
  options?: { delays?: number[] },
15
15
  ): Promise<void> {
16
16
  const delays = options?.delays ?? [250, 1000, 3000];
17
+ if (delays.length === 0) {
18
+ throw new Error("waitFor: empty delay schedule");
19
+ }
17
20
  let lastError: unknown;
18
21
 
19
22
  for (let i = 0; i < delays.length; i++) {
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Run `fn` with `globalThis.Temporal` deleted, then restore.
3
+ * Used by ambient-independence regression tests (kumiko-framework#1525/#1550).
4
+ */
5
+ export async function withoutAmbientTemporal<T>(fn: () => T | Promise<T>): Promise<T> {
6
+ const savedGlobal = (globalThis as { Temporal?: unknown }).Temporal;
7
+ delete (globalThis as { Temporal?: unknown }).Temporal;
8
+ try {
9
+ return await fn();
10
+ } finally {
11
+ if (savedGlobal === undefined) delete (globalThis as { Temporal?: unknown }).Temporal;
12
+ else (globalThis as { Temporal?: unknown }).Temporal = savedGlobal;
13
+ }
14
+ }
@@ -1 +1 @@
1
- export * from "@cosmicdrift/kumiko-types/geo-tz";
1
+ export type * from "@cosmicdrift/kumiko-types/geo-tz";
@@ -1,66 +1,49 @@
1
- // Temporal-Polyfill-Initialisierung.
1
+ // Temporal polyfill bootstrap.
2
2
  //
3
- // Hintergrund: Temporal ist seit Anfang 2026 in Chromium 144+ und Firefox 139+
4
- // nativ verfügbar, aber nicht in Safari, iOS, oder Hermes (React Native).
5
- // Bun/Node haben es teilweise (V8-abhängig, instabil).
3
+ // Temporal is native in Chromium 144+ / Firefox 139+, but missing in Safari,
4
+ // iOS, and Hermes. Bun/Node coverage is incomplete. Boot installs
5
+ // `temporal-polyfill` once so server/web/mobile share one API.
6
6
  //
7
- // Damit kumiko-Apps universal laufen Server (Bun), Web (alle Browser),
8
- // Mobile (Hermes)installiert das Framework beim Boot einmal den
9
- // `temporal-polyfill` (FullCalendar, ~25 KB). Auf Runtimes mit nativem
10
- // Temporal ist der Aufruf ein No-Op.
11
- //
12
- // Idempotent: mehrfacher Aufruf ist sicher (Polyfill prüft selbst ob
13
- // `globalThis.Temporal` schon existiert). Wir cachen das Ergebnis trotzdem
14
- // in einem Modul-Singleton, damit Boot-Performance nicht jedes Mal das
15
- // Polyfill-Modul-Loading triggert.
7
+ // Idempotent: if `globalThis.Temporal` already exists the call is a no-op.
8
+ // The cache is the live global check not a sticky module flag so tests
9
+ // that delete the ambient global (fw#1550) still re-install on the next call.
16
10
 
17
- let polyfillInstalled = false;
18
11
  let polyfillPromise: Promise<void> | null = null;
19
12
 
20
13
  /**
21
- * Stellt sicher dass `globalThis.Temporal` verfügbar ist. Idempotent.
22
- *
23
- * - Wenn Native Temporal existiert (moderne Browser, neueres Bun): No-Op.
24
- * - Sonst: lädt `temporal-polyfill/global` (installiert globalThis.Temporal).
25
- *
26
- * Wird einmal beim Framework-Boot aufgerufen. Feature-Code muss das nicht
27
- * selbst tun — `Temporal` ist nach dem Boot überall verfügbar.
14
+ * Ensure `globalThis.Temporal` is available. Idempotent.
28
15
  */
29
16
  export async function ensureTemporalPolyfill(): Promise<void> {
30
- // skip: Idempotenz — Polyfill bereits installiert in einem früheren Aufruf.
31
- if (polyfillInstalled) return;
17
+ if ("Temporal" in globalThis) {
18
+ // skip: Temporal already on globalThis (native or prior polyfill)
19
+ return;
20
+ }
32
21
  if (polyfillPromise) {
33
22
  await polyfillPromise;
34
- // skip: Concurrent-Boot anderer Aufruf hat die Installation übernommen.
35
- return;
23
+ if ("Temporal" in globalThis) {
24
+ // skip: Concurrent boot — peer call finished install
25
+ return;
26
+ }
27
+ // Peer resolved but global still missing (torn down mid-flight).
28
+ polyfillPromise = null;
36
29
  }
37
30
 
38
31
  polyfillPromise = (async () => {
39
- // Runtime-Check entgegen Type-System-Annahme: temporal-spec deklariert
40
- // Temporal ambient, aber ohne Polyfill fehlt es zur Laufzeit. `in`-Check
41
- // ist die sauberste Prüfung, ohne Cast + ohne TS-Truthy-Warnings.
42
32
  if ("Temporal" in globalThis) {
43
- polyfillInstalled = true;
44
- // skip: Native Temporal vorhanden — Polyfill nicht nötig.
33
+ // skip: raced native/peer install
45
34
  return;
46
35
  }
47
- // Polyfill globally installieren — der Side-Effect-Import setzt
48
- // globalThis.Temporal.
49
36
  await import("temporal-polyfill/global");
50
- polyfillInstalled = true;
51
37
  })();
52
38
 
53
39
  await polyfillPromise;
54
40
  }
55
41
 
56
42
  /**
57
- * Type-safe Zugriff auf globalThis.Temporal. Wirft wenn der Polyfill noch
58
- * nicht installiert ist (Boot-Reihenfolge-Bug). Feature-Code sollte
59
- * `ensureTemporalPolyfill()` einmal awaiten und danach `Temporal` global
60
- * nutzen, oder über diesen Helper die Sicherheit haben.
43
+ * Type-safe access to globalThis.Temporal. Throws if the polyfill has not
44
+ * been installed yet (boot-order bug).
61
45
  */
62
46
  export function getTemporal(): typeof Temporal {
63
- // Runtime-Check entgegen Type-System-Annahme — siehe ensureTemporalPolyfill.
64
47
  if (!("Temporal" in globalThis)) {
65
48
  throw new Error(
66
49
  "Temporal not available. Call ensureTemporalPolyfill() during framework boot before any time-related code runs.",
@@ -1,25 +1,29 @@
1
- // ctx.tz — die Feature-Code-API für TZ-Operationen.
1
+ // ctx.tz — the feature-code API for TZ operations.
2
2
  //
3
- // Eine konsistente Form für jeden Date/Time-Bedarf im Handler-Code:
4
- // - "Jetzt als UTC-Instant" → ctx.tz.now()
5
- // - "Heute in Tenant-TZ" → ctx.tz.today(ctx.tz.tenant)
6
- // - "Wall-Clock parsen" → ctx.tz.parse("2026-04-03T10:00", "Europe/Lisbon")
7
- // - "ZonedDateTime → JSON-Pair" → ctx.tz.toLocatedJson(zdt)
8
- // - "JSON-Pair → ZonedDateTime" → ctx.tz.fromLocatedJson({ at, tz })
3
+ // A consistent shape for every date/time need in handler code:
4
+ // - "now as a UTC instant" → ctx.tz.now()
5
+ // - "today in the tenant's TZ" → ctx.tz.today(ctx.tz.tenant)
6
+ // - "parse a wall-clock string" → ctx.tz.parse("2026-04-03T10:00", "Europe/Lisbon")
7
+ // - "ZonedDateTime → JSON pair" → ctx.tz.toLocatedJson(zdt)
8
+ // - "JSON pair → ZonedDateTime" → ctx.tz.fromLocatedJson({ at, tz })
9
9
  //
10
- // Feature-Code soll NICHT mehr `new Date()` aufrufen die Lint-Regel dafür
11
- // kommt in einer späteren Iteration, wenn alle existing usages migriert sind.
10
+ // Feature code should no longer call `new Date()` — the lint rule for that
11
+ // lands in a later iteration, once all existing usages are migrated.
12
12
  //
13
- // `tenant` + `user` sind die TZ-Defaults für den aktuellen Request. Aktueller
14
- // Stand: beide default auf "UTC" — sobald tenant.timezone +
15
- // user.timezone Felder existieren, lese ich sie aus dem Request-Context.
13
+ // `tenant` + `user` are the TZ defaults for the current request. Currently
14
+ // both default to "UTC" — once tenant.timezone + user.timezone fields
15
+ // exist, they get read from the request context here.
16
16
  //
17
- // Die reinen Type-Contracts (TzContext, TzContextOptions, LocatedTimestampJson)
18
- // leben in @cosmicdrift/kumiko-types/tz-context — hier nur die Factories.
17
+ // The pure type contracts (TzContext, TzContextOptions, LocatedTimestampJson)
18
+ // live in @cosmicdrift/kumiko-types/tz-context — only the factories are here.
19
19
 
20
20
  import type { TzContext, TzContextOptions } from "@cosmicdrift/kumiko-types/tz-context";
21
- import { ensureTemporalPolyfill, getTemporal } from "./polyfill";
21
+ import { Temporal as TemporalPolyfill } from "temporal-polyfill";
22
+ import { ensureTemporalPolyfill } from "./polyfill";
22
23
 
24
+ // Back-compat shim: re-exported so existing `from "@cosmicdrift/kumiko-framework/time/tz-context"`
25
+ // imports keep working. Prefer importing from @cosmicdrift/kumiko-types/tz-context directly in
26
+ // new code — drop this re-export once no framework-path imports remain.
23
27
  export type {
24
28
  LocatedTimestampJson,
25
29
  TzContext,
@@ -27,12 +31,11 @@ export type {
27
31
  } from "@cosmicdrift/kumiko-types/tz-context";
28
32
 
29
33
  /**
30
- * Factory: erzeugt einen TzContext für den aktuellen Request.
31
- * Erwartet dass ensureTemporalPolyfill() bereits gelaufen ist (passiert beim
32
- * Framework-Boot). Wenn nicht, wirft getTemporal() — kein silent failure.
34
+ * Factory: creates a TzContext for the current request.
35
+ * Uses temporal-polyfill's module export (not globalThis.Temporal) so
36
+ * buildHandlerContext stays ambient-free (fw#1525/#1550).
33
37
  */
34
38
  export function createTzContext(options: TzContextOptions = {}): TzContext {
35
- const T = getTemporal();
36
39
  const tenant = options.tenant ?? "UTC";
37
40
  const user = options.user ?? tenant;
38
41
  const geoTz = options.geoTz;
@@ -40,25 +43,26 @@ export function createTzContext(options: TzContextOptions = {}): TzContext {
40
43
  return {
41
44
  tenant,
42
45
  user,
43
- now: () => T.Now.instant(), // @wrapper-known semantic-alias
44
- nowIn: (tz: string) => T.Now.zonedDateTimeISO(tz), // @wrapper-known semantic-alias
45
- today: (tz: string) => T.Now.plainDateISO(tz), // @wrapper-known semantic-alias
46
+ now: () => TemporalPolyfill.Now.instant(), // @wrapper-known semantic-alias
47
+ nowIn: (tz: string) => TemporalPolyfill.Now.zonedDateTimeISO(tz), // @wrapper-known semantic-alias
48
+ today: (tz: string) => TemporalPolyfill.Now.plainDateISO(tz), // @wrapper-known semantic-alias
46
49
  todayRange: (tz: string) => {
47
- const today = T.Now.plainDateISO(tz);
50
+ const today = TemporalPolyfill.Now.plainDateISO(tz);
48
51
  const startZdt = today.toZonedDateTime({ timeZone: tz });
49
52
  const endZdt = today.add({ days: 1 }).toZonedDateTime({ timeZone: tz });
50
53
  return { start: startZdt.toInstant(), end: endZdt.toInstant() };
51
54
  },
52
- parse: (wallClock: string, tz: string) => T.PlainDateTime.from(wallClock).toZonedDateTime(tz),
55
+ parse: (wallClock: string, tz: string) =>
56
+ TemporalPolyfill.PlainDateTime.from(wallClock).toZonedDateTime(tz),
53
57
  toInstant: (zdt) => zdt.toInstant(),
54
58
  toLocatedJson: (zdt) => ({
55
- // Wall-Clock OHNE Offset (kein "Z", kein "+01:00") plus IANA-Name.
56
- // .toPlainDateTime().toString() liefert "YYYY-MM-DDTHH:MM:SS[.fff]"
57
- // ohne Offsetexakt unser Vertrag.
59
+ // Wall-clock WITHOUT offset (no "Z", no "+01:00") plus the IANA name.
60
+ // .toPlainDateTime().toString() returns "YYYY-MM-DDTHH:MM:SS[.fff]"
61
+ // without an offset exactly our contract.
58
62
  at: zdt.toPlainDateTime().toString(),
59
63
  tz: zdt.timeZoneId,
60
64
  }),
61
- fromLocatedJson: (obj) => T.PlainDateTime.from(obj.at).toZonedDateTime(obj.tz),
65
+ fromLocatedJson: (obj) => TemporalPolyfill.PlainDateTime.from(obj.at).toZonedDateTime(obj.tz),
62
66
  fromCoordinates: async (coords) => {
63
67
  if (geoTz === undefined) {
64
68
  throw new Error(
@@ -80,12 +84,14 @@ export function createTzContext(options: TzContextOptions = {}): TzContext {
80
84
  }
81
85
  return geoTz.fromAddress(address);
82
86
  },
83
- };
87
+ // @cast-boundary temporal-polyfill-vs-ambient: same TC39 Temporal values at
88
+ // runtime; TzContext is typed against ambient Temporal from temporal-spec.
89
+ } as TzContext;
84
90
  }
85
91
 
86
92
  /**
87
- * Convenience: stellt sicher dass der Polyfill geladen ist UND erzeugt
88
- * den TzContext in einem await. Bevorzugt verwenden in Boot-Code.
93
+ * Convenience: ensures the polyfill is loaded AND creates the TzContext in
94
+ * one await. Prefer this in boot code.
89
95
  */
90
96
  export async function createTzContextAsync(options?: TzContextOptions): Promise<TzContext> {
91
97
  await ensureTemporalPolyfill();
@@ -0,0 +1,18 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import { Temporal as PolyfillTemporal } from "temporal-polyfill";
3
+ import { ensureTemporalPolyfill } from "../../time/polyfill";
4
+ import { stringifyJson } from "../safe-json";
5
+
6
+ describe("stringifyJson — Temporal.Instant (native + polyfill)", () => {
7
+ test("serializes polyfill Instant", () => {
8
+ const instant = PolyfillTemporal.Instant.from("2026-01-01T00:00:00Z");
9
+ expect(stringifyJson({ at: instant })).toBe('{"at":"2026-01-01T00:00:00Z"}');
10
+ });
11
+
12
+ test("serializes ambient/native Instant when present", async () => {
13
+ await ensureTemporalPolyfill();
14
+ const T = (globalThis as unknown as { Temporal: typeof PolyfillTemporal }).Temporal;
15
+ const instant = T.Instant.from("2026-06-15T12:00:00Z");
16
+ expect(stringifyJson({ at: instant })).toBe('{"at":"2026-06-15T12:00:00Z"}');
17
+ });
18
+ });
@@ -29,6 +29,15 @@ export function parseJsonOrThrow<T>(raw: string, context: string): T {
29
29
  }
30
30
  }
31
31
 
32
+ /** True for native or polyfill Temporal.Instant (distinct constructors). */
33
+ function isTemporalInstant(v: unknown): v is { toString(): string } {
34
+ return (
35
+ typeof v === "object" &&
36
+ v !== null &&
37
+ Object.prototype.toString.call(v) === "[object Temporal.Instant]"
38
+ );
39
+ }
40
+
32
41
  /** JSON.stringify that survives BigInt / Temporal values from DB rows. */
33
42
  export function stringifyJson(value: unknown): string {
34
43
  return JSON.stringify(value, (_key, v) => {
@@ -43,7 +52,10 @@ export function stringifyJson(value: unknown): string {
43
52
  }
44
53
  return v.toString();
45
54
  }
46
- if (v instanceof Temporal.Instant) return v.toString();
55
+ // Duck-tag, not instanceof: ensureTemporalPolyfill keeps native
56
+ // globalThis.Temporal when present, so DB Instants may be native while
57
+ // other code paths use temporal-polyfill's Instant (fw#1550 / Bugbot).
58
+ if (isTemporalInstant(v)) return v.toString();
47
59
  return v;
48
60
  });
49
61
  }
@@ -1,80 +0,0 @@
1
- // Dilution reclaim: integration lcov registers every facade getter as DA:N,0
2
- // when the file loads, but unit only instruments getters that a test touches.
3
- // Calling every getter once in unit raises LF+LH so the badge merge stops
4
- // counting those one-liners as uncovered.
5
-
6
- import { describe, expect, test } from "bun:test";
7
- import { createRegistry } from "../registry";
8
-
9
- describe("buildRegistryFacade — getter sweep", () => {
10
- test("empty registry: every getter is callable without throw", () => {
11
- const r = createRegistry([]);
12
-
13
- expect(r.features.size).toBe(0);
14
- expect(r.getFeature("x")).toBeUndefined();
15
- expect(r.hasRateLimitedHandler()).toBe(false);
16
- expect(r.getEntity("x")).toBeUndefined();
17
- expect(r.getAllEntities().size).toBe(0);
18
- expect(r.getWriteHandler("x")).toBeUndefined();
19
- expect(r.getQueryHandler("x")).toBeUndefined();
20
- expect(r.getAllQueryHandlers().size).toBe(0);
21
- expect(r.getSearchableFields("x")).toEqual([]);
22
- expect(r.getSortableFields("x")).toEqual([]);
23
- expect(r.getRelations("x")).toEqual({});
24
- expect(r.getSearchIncludes("x").size).toBe(0);
25
- expect(r.getIncomingRelations("x")).toEqual([]);
26
- expect(r.getPreSaveHooks("x")).toEqual([]);
27
- expect(r.getPostSaveHooks("x")).toEqual([]);
28
- expect(r.getPreDeleteHooks("x")).toEqual([]);
29
- expect(r.getPostDeleteHooks("x")).toEqual([]);
30
- expect(r.getPreQueryHooks("x")).toEqual([]);
31
- expect(r.getPostQueryHooks("x")).toEqual([]);
32
- expect(r.getEntityPostSaveHooks("x")).toEqual([]);
33
- expect(r.getEntityPreDeleteHooks("x")).toEqual([]);
34
- expect(r.getEntityPostDeleteHooks("x")).toEqual([]);
35
- expect(r.getEntityPostQueryHooks("x")).toEqual([]);
36
- expect(r.getSearchPayloadExtensions("x")).toEqual([]);
37
- expect(r.getAllTranslations()).toBeDefined();
38
- expect(r.getHandlerEntity("x")).toBeUndefined();
39
- expect(r.isHandlerSystemScoped("x")).toBe(false);
40
- expect(r.getHandlerFeature("x")).toBeUndefined();
41
- expect(r.getAllMetrics().size).toBe(0);
42
- expect(r.getAllSecretKeys().size).toBe(0);
43
- expect(r.getSecretKey("x")).toBeUndefined();
44
- expect(r.getConfigKey("x")).toBeUndefined();
45
- expect(r.getAllConfigKeys().size).toBe(0);
46
- expect(r.getJob("x")).toBeUndefined();
47
- expect(r.getAllJobs().size).toBe(0);
48
- expect(r.getEvent("x")).toBeUndefined();
49
- expect(r.getEventUpcasters().size).toBe(0);
50
- expect(r.getExtension("x")).toBeUndefined();
51
- expect(r.getExtensionUsages("x")).toEqual([]);
52
- expect(r.getAllExtensionSelectors().size).toBe(0);
53
- expect(r.getAllNotifications().size).toBe(0);
54
- expect(r.getAllReferenceData()).toEqual([]);
55
- expect(r.getAllConfigSeeds()).toEqual([]);
56
- expect(r.getProjectionsForSource("x")).toEqual([]);
57
- expect(r.getAllProjections().size).toBe(0);
58
- expect(r.getAllStoreTables().size).toBe(0);
59
- expect(r.getAllMultiStreamProjections().size).toBe(0);
60
- expect(r.getMultiStreamProjectionFeature("x")).toBeUndefined();
61
- expect(r.getAuthClaimsHooks()).toEqual([]);
62
- expect(r.getAllClaimKeys().size).toBe(0);
63
- expect(r.getClaimKey("x")).toBeUndefined();
64
- expect(r.getAllScreens().size).toBe(0);
65
- expect(r.getScreen("x")).toBeUndefined();
66
- expect(r.getScreenFeature("x")).toBeUndefined();
67
- expect(r.getScreensByEntity("x")).toEqual([]);
68
- expect(r.getAllNavs().size).toBe(0);
69
- expect(r.getNav("x")).toBeUndefined();
70
- expect(r.getNavFeature("x")).toBeUndefined();
71
- expect(r.getNavsByParent("x")).toEqual([]);
72
- expect(r.getTopLevelNavs()).toEqual([]);
73
- expect(r.getAllWorkspaces().size).toBe(0);
74
- expect(r.getWorkspace("x")).toBeUndefined();
75
- expect(r.getWorkspaceFeature("x")).toBeUndefined();
76
- expect(r.getWorkspaceNavs("x")).toEqual([]);
77
- expect(r.getDefaultWorkspace()).toBeUndefined();
78
- expect(r.getTreeActions("x")).toBeUndefined();
79
- });
80
- });