@cosmicdrift/kumiko-framework 0.95.0 → 0.97.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
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cosmicdrift/kumiko-framework",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.97.0",
|
|
4
4
|
"description": "Framework core — engine, pipeline, API, DB, and every other bit that makes Kumiko go.",
|
|
5
5
|
"license": "BUSL-1.1",
|
|
6
6
|
"author": "Marc Frost <marc@cosmicdriftgamestudio.com>",
|
|
@@ -181,7 +181,7 @@
|
|
|
181
181
|
"zod": "^4.4.3"
|
|
182
182
|
},
|
|
183
183
|
"devDependencies": {
|
|
184
|
-
"@cosmicdrift/kumiko-dispatcher-live": "0.
|
|
184
|
+
"@cosmicdrift/kumiko-dispatcher-live": "0.97.0",
|
|
185
185
|
"bun-types": "^1.3.13",
|
|
186
186
|
"pino-pretty": "^13.1.3"
|
|
187
187
|
},
|
|
@@ -1920,6 +1920,28 @@ describe("boot-validator", () => {
|
|
|
1920
1920
|
/rowAction "edit" navigate-target "ghost-screen" does not resolve/,
|
|
1921
1921
|
);
|
|
1922
1922
|
});
|
|
1923
|
+
|
|
1924
|
+
test("navigate-target → screen in ANOTHER feature → kein Throw (cross-feature)", () => {
|
|
1925
|
+
// Eine deklarative Liste im owning-Feature der Entity navigiert zum
|
|
1926
|
+
// Custom-Editor der Consumer-App (anderes Feature). Runtime löst die bare
|
|
1927
|
+
// id app-weit auf → der Validator muss das erlauben.
|
|
1928
|
+
const list = defineFeature("shop", (r) => {
|
|
1929
|
+
r.entity("product", createEntity({ fields: { name: createTextField() } }));
|
|
1930
|
+
r.screen({
|
|
1931
|
+
id: "product-list",
|
|
1932
|
+
type: "entityList",
|
|
1933
|
+
entity: "product",
|
|
1934
|
+
columns: ["name"],
|
|
1935
|
+
rowActions: [
|
|
1936
|
+
{ kind: "navigate", id: "edit", label: "actions.edit", screen: "product-editor" },
|
|
1937
|
+
],
|
|
1938
|
+
});
|
|
1939
|
+
});
|
|
1940
|
+
const consumer = defineFeature("app", (r) => {
|
|
1941
|
+
r.screen({ id: "product-editor", type: "custom", renderer: { react: "stub" } });
|
|
1942
|
+
});
|
|
1943
|
+
expect(() => validateBoot([list, consumer])).not.toThrow();
|
|
1944
|
+
});
|
|
1923
1945
|
});
|
|
1924
1946
|
|
|
1925
1947
|
// --- rowAction kind="writeHandler" handler-QN-Validierung (Tier 2.7e-1 erw.) ---
|
|
@@ -2125,6 +2147,25 @@ describe("boot-validator", () => {
|
|
|
2125
2147
|
/toolbarAction "open-form" navigate-target "ghost-form" does not resolve/,
|
|
2126
2148
|
);
|
|
2127
2149
|
});
|
|
2150
|
+
|
|
2151
|
+
test("navigate-target → screen in ANOTHER feature → kein Throw (cross-feature)", () => {
|
|
2152
|
+
const list = defineFeature("shop", (r) => {
|
|
2153
|
+
r.entity("product", createEntity({ fields: { name: createTextField() } }));
|
|
2154
|
+
r.screen({
|
|
2155
|
+
id: "product-list",
|
|
2156
|
+
type: "entityList",
|
|
2157
|
+
entity: "product",
|
|
2158
|
+
columns: ["name"],
|
|
2159
|
+
toolbarActions: [
|
|
2160
|
+
{ kind: "navigate", id: "new", label: "actions.new", screen: "product-editor" },
|
|
2161
|
+
],
|
|
2162
|
+
});
|
|
2163
|
+
});
|
|
2164
|
+
const consumer = defineFeature("app", (r) => {
|
|
2165
|
+
r.screen({ id: "product-editor", type: "custom", renderer: { react: "stub" } });
|
|
2166
|
+
});
|
|
2167
|
+
expect(() => validateBoot([list, consumer])).not.toThrow();
|
|
2168
|
+
});
|
|
2128
2169
|
});
|
|
2129
2170
|
|
|
2130
2171
|
describe("entityList toolbarAction writeHandler handler-QN (Tier 2.7e-2)", () => {
|
|
@@ -88,6 +88,13 @@ export function validateScreens(
|
|
|
88
88
|
allScreenQns: ReadonlySet<string>,
|
|
89
89
|
allConfigKeyQns: ReadonlySet<string>,
|
|
90
90
|
): void {
|
|
91
|
+
// navigate-Targets (rowAction/toolbarAction) dürfen cross-feature zeigen —
|
|
92
|
+
// der Runtime-Router (create-app) löst eine bare screenId app-weit über ALLE
|
|
93
|
+
// Features auf (eine deklarative Liste im owning-Feature der Entity navigiert
|
|
94
|
+
// so zu den Custom-Editoren der Consumer-App). Der Validator spiegelt das:
|
|
95
|
+
// same-feature ODER irgendein Feature. (redirect/cancelTarget bleiben bewusst
|
|
96
|
+
// same-feature: deren Router baut die URL direkt aus der kurzen id.)
|
|
97
|
+
const navTargetShortIds = screenShortIdsFrom(allScreenQns);
|
|
91
98
|
for (const [screenId, screen] of Object.entries(feature.screens)) {
|
|
92
99
|
if (screen.type === "custom") {
|
|
93
100
|
if (!screen.renderer.react && !screen.renderer.native) {
|
|
@@ -429,10 +436,10 @@ export function validateScreens(
|
|
|
429
436
|
for (const action of screen.rowActions) {
|
|
430
437
|
if (action.kind === "navigate") {
|
|
431
438
|
const candidateQn = qualifyEntityName(feature.name, "screen", action.screen);
|
|
432
|
-
if (!allScreenQns.has(candidateQn)) {
|
|
439
|
+
if (!allScreenQns.has(candidateQn) && !navTargetShortIds.has(action.screen)) {
|
|
433
440
|
throw new Error(
|
|
434
441
|
`[Feature ${feature.name}] Screen "${screenId}" (entityList) rowAction "${action.id}" ` +
|
|
435
|
-
`navigate-target "${action.screen}" does not resolve to a registered screen in
|
|
442
|
+
`navigate-target "${action.screen}" does not resolve to a registered screen in any feature.`,
|
|
436
443
|
);
|
|
437
444
|
}
|
|
438
445
|
} else {
|
|
@@ -462,10 +469,10 @@ export function validateScreens(
|
|
|
462
469
|
for (const action of screen.toolbarActions) {
|
|
463
470
|
if (action.kind === "navigate") {
|
|
464
471
|
const candidateQn = qualifyEntityName(feature.name, "screen", action.screen);
|
|
465
|
-
if (!allScreenQns.has(candidateQn)) {
|
|
472
|
+
if (!allScreenQns.has(candidateQn) && !navTargetShortIds.has(action.screen)) {
|
|
466
473
|
throw new Error(
|
|
467
474
|
`[Feature ${feature.name}] Screen "${screenId}" (entityList) toolbarAction "${action.id}" ` +
|
|
468
|
-
`navigate-target "${action.screen}" does not resolve to a registered screen in
|
|
475
|
+
`navigate-target "${action.screen}" does not resolve to a registered screen in any feature.`,
|
|
469
476
|
);
|
|
470
477
|
}
|
|
471
478
|
} else {
|
|
@@ -615,6 +622,19 @@ export function collectScreenQns(features: readonly FeatureDefinition[]): Set<st
|
|
|
615
622
|
return set;
|
|
616
623
|
}
|
|
617
624
|
|
|
625
|
+
// Bare Screen-ids (ohne `<feature>:screen:`-Prefix) aus den qualifizierten
|
|
626
|
+
// QNs — für die app-weite Auflösung von navigate-Targets (s. validateScreens).
|
|
627
|
+
// Spiegelt den Runtime-Router, der bare ids feature-übergreifend matcht.
|
|
628
|
+
export function screenShortIdsFrom(allScreenQns: ReadonlySet<string>): Set<string> {
|
|
629
|
+
const marker = ":screen:";
|
|
630
|
+
const set = new Set<string>();
|
|
631
|
+
for (const qn of allScreenQns) {
|
|
632
|
+
const at = qn.indexOf(marker);
|
|
633
|
+
if (at !== -1) set.add(qn.slice(at + marker.length));
|
|
634
|
+
}
|
|
635
|
+
return set;
|
|
636
|
+
}
|
|
637
|
+
|
|
618
638
|
// Sammelt alle qualifizierten Write-Handler-QNs (`<feature>:write:<short>`).
|
|
619
639
|
// Wird vom actionForm-Screen-Validator genutzt um zu prüfen ob der
|
|
620
640
|
// im Schema deklarierte handler tatsächlich registriert ist —
|