@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.
- package/package.json +5 -3
- package/src/__tests__/consumer-cli.integration.test.ts +32 -0
- package/src/__tests__/schema-cli.integration.test.ts +1 -1
- package/src/api/__tests__/api.test.ts +267 -19
- package/src/api/__tests__/auth-middleware-anonymous-access-boot.test.ts +40 -0
- package/src/api/__tests__/auth-routes-invalid-body-invite.test.ts +16 -0
- package/src/api/__tests__/auth-routes-mfa-preauth-confirm.test.ts +2 -1
- package/src/api/__tests__/auth-routes-mfa-preauth-enable-start.test.ts +64 -1
- package/src/api/__tests__/auth-routes-trusted-proxy.test.ts +135 -0
- package/src/api/__tests__/batch.integration.test.ts +21 -2
- package/src/api/__tests__/jwt.test.ts +52 -2
- package/src/api/__tests__/redis-login-rate-limiter.integration.test.ts +72 -0
- package/src/api/__tests__/sse-broker.test.ts +57 -0
- package/src/api/__tests__/sse-route.test.ts +4 -0
- package/src/api/auth-routes.ts +165 -33
- package/src/api/index.ts +1 -0
- package/src/api/jwt.ts +22 -1
- package/src/api/routes.ts +103 -35
- package/src/api/server.ts +17 -1
- package/src/api/sse-broker.ts +39 -0
- package/src/bun-db/index.ts +1 -0
- package/src/bun-db/query.ts +12 -3
- package/src/consumer-cli.ts +60 -13
- package/src/db/__tests__/event-store-executor-write-verbs.integration.test.ts +14 -1
- package/src/db/__tests__/located-timestamp.test.ts +19 -0
- package/src/db/__tests__/migrate-runner.test.ts +61 -0
- package/src/db/__tests__/replay-migration-sql.test.ts +131 -2
- package/src/db/__tests__/tenant-db-where-merge.test.ts +6 -2
- package/src/db/api.ts +2 -2
- package/src/db/bun-provider.ts +2 -2
- package/src/db/connection.ts +6 -3
- package/src/db/dialect.ts +1 -6
- package/src/db/entity-table-meta-types.ts +1 -1
- package/src/db/event-store-executor-context.ts +2 -3
- package/src/db/event-store-executor-read.ts +2 -3
- package/src/db/event-store-executor-write.ts +8 -0
- package/src/db/index.ts +8 -1
- package/src/db/located-timestamp.ts +4 -0
- package/src/db/migrate-runner.ts +107 -11
- package/src/db/pg-error.ts +8 -0
- package/src/db/postgres-provider.ts +2 -2
- package/src/db/queries/__tests__/event-store-idempotency-index.integration.test.ts +80 -0
- package/src/db/queries/ddl.ts +45 -0
- package/src/db/queries/event-store.ts +97 -5
- package/src/db/queries/test-stack.ts +4 -30
- package/src/db/reference-data.ts +2 -3
- package/src/db/replay-migration-sql.ts +114 -12
- package/src/db/tenant-db.ts +2 -4
- package/src/engine/__tests__/engine.test.ts +30 -0
- package/src/engine/__tests__/schema-builder.test.ts +18 -0
- package/src/engine/__tests__/store-table.test.ts +2 -2
- package/src/engine/boot-validator/nav.ts +5 -0
- package/src/engine/constants.ts +32 -6
- package/src/engine/create-app.ts +11 -0
- package/src/engine/effective-features.ts +12 -2
- package/src/engine/extensions/user-data.ts +12 -4
- package/src/engine/feature-ui-extensions.ts +2 -2
- package/src/engine/hook-helpers.ts +3 -1
- package/src/engine/index.ts +1 -1
- package/src/engine/ownership.ts +4 -3
- package/src/engine/registry-ingest.ts +14 -14
- package/src/engine/registry-state.ts +4 -1
- package/src/engine/schema-builder.ts +1 -0
- package/src/engine/steps/__tests__/duration-utils.test.ts +20 -0
- package/src/engine/steps/_duration-utils.ts +2 -0
- package/src/engine/steps/unsafe-projection-upsert.ts +1 -4
- package/src/engine/types/config.ts +1 -1
- package/src/engine/types/define-handler.ts +1 -1
- package/src/engine/types/entity-handlers.ts +1 -1
- package/src/engine/types/event-type-map.ts +1 -1
- package/src/engine/types/feature.ts +1 -1
- package/src/engine/types/fields.ts +1 -1
- package/src/engine/types/handlers.ts +1 -1
- package/src/engine/types/hooks.ts +1 -1
- package/src/engine/types/http-route.ts +1 -1
- package/src/engine/types/nav.ts +1 -1
- package/src/engine/types/ownership.ts +1 -1
- package/src/engine/types/projection.ts +1 -1
- package/src/engine/types/relations.ts +1 -1
- package/src/engine/types/screen.ts +1 -1
- package/src/engine/types/step.ts +1 -1
- package/src/engine/types/target-ref.ts +1 -1
- package/src/engine/types/tree-node.ts +1 -1
- package/src/engine/types/workspace.ts +1 -1
- package/src/engine/validate-projection-allowlist.ts +5 -5
- package/src/errors/classes.ts +21 -0
- package/src/errors/index.ts +1 -0
- package/src/errors/write-error-info.ts +6 -2
- package/src/event-store/__tests__/admin-api.integration.test.ts +27 -1
- package/src/event-store/__tests__/event-store.integration.test.ts +32 -0
- package/src/event-store/__tests__/unscoped-stream-primitives.guard.test.ts +22 -4
- package/src/event-store/admin-api.ts +11 -4
- package/src/event-store/event-store.ts +19 -4
- package/src/event-store/types.ts +1 -1
- package/src/files/__tests__/build-storage-key.test.ts +28 -0
- package/src/files/__tests__/local-provider.test.ts +31 -0
- package/src/files/__tests__/write-stream.test.ts +3 -3
- package/src/files/index.ts +1 -1
- package/src/files/local-provider.ts +6 -1
- package/src/files/types.ts +8 -1
- package/src/jobs/__tests__/jobs.integration.test.ts +167 -7
- package/src/jobs/job-runner.ts +41 -11
- package/src/logging/types.ts +1 -1
- package/src/observability/index.ts +1 -0
- package/src/observability/standard-metrics.ts +35 -2
- package/src/observability/types/index.ts +1 -1
- package/src/observability/types/metric.ts +1 -1
- package/src/observability/types/provider.ts +1 -1
- package/src/observability/types/span.ts +1 -1
- package/src/pipeline/__tests__/dispatcher.test.ts +151 -0
- package/src/pipeline/__tests__/event-consumer-state.integration.test.ts +31 -0
- package/src/pipeline/__tests__/event-dispatcher-rearm.integration.test.ts +83 -0
- package/src/pipeline/__tests__/lifecycle-pipeline.test.ts +107 -97
- package/src/pipeline/dispatch-shared.ts +59 -6
- package/src/pipeline/dispatch-stream.ts +32 -11
- package/src/pipeline/dispatcher.ts +7 -1
- package/src/pipeline/event-consumer-state.ts +16 -13
- package/src/pipeline/event-dispatcher-delivery.ts +20 -6
- package/src/pipeline/event-dispatcher.ts +22 -0
- package/src/pipeline/index.ts +2 -0
- package/src/pipeline/system-hooks.ts +87 -0
- package/src/rate-limit/__tests__/resolver.integration.test.ts +18 -0
- package/src/rate-limit/resolver.ts +6 -2
- package/src/schema-cli.ts +24 -12
- package/src/search/__tests__/reindex-entity.integration.test.ts +24 -1
- package/src/search/reindex-entity.ts +31 -2
- package/src/search/types.ts +1 -1
- package/src/stack/__tests__/setup-test-stack-jobs.integration.test.ts +6 -2
- package/src/stack/db.ts +2 -1
- package/src/stack/push-entity-projection-tables.ts +2 -1
- package/src/stack/request-helper.ts +20 -1
- package/src/stack/table-helpers.ts +6 -4
- package/src/stack/test-stack.ts +18 -15
- package/src/testing/__tests__/late-bound.test.ts +7 -0
- package/src/testing/__tests__/wait-for.test.ts +6 -0
- package/src/testing/file-provider-contract.ts +26 -6
- package/src/testing/index.ts +1 -0
- package/src/testing/late-bound.ts +5 -3
- package/src/testing/wait-for.ts +3 -0
- package/src/testing/without-ambient-temporal.ts +14 -0
- package/src/time/geo-tz.ts +1 -1
- package/src/time/polyfill.ts +21 -38
- package/src/time/tz-context.ts +37 -31
- package/src/utils/__tests__/safe-json-temporal.test.ts +18 -0
- package/src/utils/safe-json.ts +13 -1
- package/src/engine/__tests__/registry-facade-sweep.test.ts +0 -80
package/src/testing/wait-for.ts
CHANGED
|
@@ -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
|
+
}
|
package/src/time/geo-tz.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export * from "@cosmicdrift/kumiko-types/geo-tz";
|
|
1
|
+
export type * from "@cosmicdrift/kumiko-types/geo-tz";
|
package/src/time/polyfill.ts
CHANGED
|
@@ -1,66 +1,49 @@
|
|
|
1
|
-
// Temporal
|
|
1
|
+
// Temporal polyfill bootstrap.
|
|
2
2
|
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
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
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
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
|
-
*
|
|
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
|
-
|
|
31
|
-
|
|
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
|
-
|
|
35
|
-
|
|
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
|
-
|
|
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
|
|
58
|
-
*
|
|
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.",
|
package/src/time/tz-context.ts
CHANGED
|
@@ -1,25 +1,29 @@
|
|
|
1
|
-
// ctx.tz —
|
|
1
|
+
// ctx.tz — the feature-code API for TZ operations.
|
|
2
2
|
//
|
|
3
|
-
//
|
|
4
|
-
// - "
|
|
5
|
-
// - "
|
|
6
|
-
// - "
|
|
7
|
-
// - "ZonedDateTime → JSON
|
|
8
|
-
// - "JSON
|
|
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
|
|
11
|
-
//
|
|
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`
|
|
14
|
-
//
|
|
15
|
-
//
|
|
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
|
-
//
|
|
18
|
-
//
|
|
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 {
|
|
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:
|
|
31
|
-
*
|
|
32
|
-
*
|
|
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: () =>
|
|
44
|
-
nowIn: (tz: string) =>
|
|
45
|
-
today: (tz: string) =>
|
|
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 =
|
|
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) =>
|
|
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-
|
|
56
|
-
// .toPlainDateTime().toString()
|
|
57
|
-
//
|
|
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) =>
|
|
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:
|
|
88
|
-
*
|
|
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
|
+
});
|
package/src/utils/safe-json.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
});
|