@jskit-ai/shell-web 0.1.4
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.descriptor.mjs +165 -0
- package/package.json +23 -0
- package/src/client/components/ShellErrorHost.vue +208 -0
- package/src/client/components/ShellLayout.vue +191 -0
- package/src/client/components/ShellOutlet.vue +95 -0
- package/src/client/components/useShellLayout.js +93 -0
- package/src/client/error/index.js +2 -0
- package/src/client/error/inject.js +142 -0
- package/src/client/error/normalize.js +75 -0
- package/src/client/error/policy.js +50 -0
- package/src/client/error/presenters.js +89 -0
- package/src/client/error/runtime.js +418 -0
- package/src/client/error/store.js +176 -0
- package/src/client/error/tokens.js +14 -0
- package/src/client/index.js +17 -0
- package/src/client/navigation/linkResolver.js +117 -0
- package/src/client/placement/debug.js +52 -0
- package/src/client/placement/index.js +26 -0
- package/src/client/placement/inject.js +104 -0
- package/src/client/placement/pathname.js +14 -0
- package/src/client/placement/registry.js +41 -0
- package/src/client/placement/runtime.js +435 -0
- package/src/client/placement/surfaceContext.js +290 -0
- package/src/client/placement/tokens.js +29 -0
- package/src/client/placement/validators.js +210 -0
- package/src/client/providers/ShellWebClientProvider.js +352 -0
- package/templates/src/App.vue +11 -0
- package/templates/src/components/ShellLayout.vue +247 -0
- package/templates/src/error.js +13 -0
- package/templates/src/pages/console/index.vue +24 -0
- package/templates/src/pages/console.vue +20 -0
- package/templates/src/pages/home/index.vue +54 -0
- package/templates/src/pages/home.vue +20 -0
- package/templates/src/placement.js +12 -0
- package/test/errorRuntime.test.js +191 -0
- package/test/errorStore.test.js +26 -0
- package/test/linkResolver.test.js +112 -0
- package/test/placementRegistry.test.js +45 -0
- package/test/placementRuntime.test.js +374 -0
- package/test/provider.test.js +163 -0
- package/test/surfaceContext.test.js +184 -0
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { unref } from "vue";
|
|
2
|
+
import { resolveLinkPath, normalizePathname } from "@jskit-ai/kernel/shared";
|
|
3
|
+
import { normalizeSurfaceId } from "@jskit-ai/kernel/shared/surface/registry";
|
|
4
|
+
import { useWebPlacementContext } from "../placement/inject.js";
|
|
5
|
+
import {
|
|
6
|
+
resolveSurfaceDefinitionFromPlacementContext,
|
|
7
|
+
resolveSurfaceRootPathFromPlacementContext
|
|
8
|
+
} from "../placement/surfaceContext.js";
|
|
9
|
+
|
|
10
|
+
function normalizeParamsMap(params = null) {
|
|
11
|
+
if (!params || typeof params !== "object" || Array.isArray(params)) {
|
|
12
|
+
return {};
|
|
13
|
+
}
|
|
14
|
+
return params;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function materializeSurfaceRouteBase(routeBaseTemplate = "/", { params = {}, strictParams = true, surface = "" } = {}) {
|
|
18
|
+
const normalizedParams = normalizeParamsMap(params);
|
|
19
|
+
const missingParams = new Set();
|
|
20
|
+
const outputPath = String(routeBaseTemplate || "/").replace(/:([A-Za-z0-9_]+)/g, (_full, rawName) => {
|
|
21
|
+
const paramName = String(rawName || "").trim();
|
|
22
|
+
const paramValue = normalizedParams[paramName];
|
|
23
|
+
const normalizedValue = String(paramValue ?? "").trim();
|
|
24
|
+
if (!normalizedValue) {
|
|
25
|
+
missingParams.add(paramName);
|
|
26
|
+
return `:${paramName}`;
|
|
27
|
+
}
|
|
28
|
+
return encodeURIComponent(normalizedValue);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
if (strictParams && missingParams.size > 0) {
|
|
32
|
+
const surfaceLabel = String(surface || "").trim() || "(default)";
|
|
33
|
+
const missing = [...missingParams].sort().join(", ");
|
|
34
|
+
throw new Error(`Missing required surface route params for "${surfaceLabel}": ${missing}.`);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return outputPath;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function resolveSurfaceBasePath(context = null, surface = "", { params = {}, strictParams = true } = {}) {
|
|
41
|
+
const normalizedSurface = normalizeSurfaceId(surface);
|
|
42
|
+
if (normalizedSurface && resolveSurfaceDefinitionFromPlacementContext(context, normalizedSurface)) {
|
|
43
|
+
const routeBaseTemplate = resolveSurfaceRootPathFromPlacementContext(context, normalizedSurface);
|
|
44
|
+
return materializeSurfaceRouteBase(routeBaseTemplate, {
|
|
45
|
+
params,
|
|
46
|
+
strictParams,
|
|
47
|
+
surface: normalizedSurface
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (!normalizedSurface) {
|
|
52
|
+
return "/";
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return normalizePathname(`/${normalizedSurface}`);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function resolveShellLinkPath({
|
|
59
|
+
context = null,
|
|
60
|
+
surface = "",
|
|
61
|
+
explicitTo = "",
|
|
62
|
+
relativePath = "/",
|
|
63
|
+
surfaceRelativePath = "",
|
|
64
|
+
params = {},
|
|
65
|
+
strictParams = true
|
|
66
|
+
} = {}) {
|
|
67
|
+
const explicitTarget = String(explicitTo || "").trim();
|
|
68
|
+
if (explicitTarget) {
|
|
69
|
+
return explicitTarget;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const normalizedSurface = normalizeSurfaceId(surface);
|
|
73
|
+
const nextRelativePath = String(surfaceRelativePath || "").trim() || String(relativePath || "").trim() || "/";
|
|
74
|
+
const nextSurfaceBasePath = resolveSurfaceBasePath(context, normalizedSurface, {
|
|
75
|
+
params,
|
|
76
|
+
strictParams
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
return resolveLinkPath(nextSurfaceBasePath, nextRelativePath);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function useShellLinkResolver({ surface = "" } = {}) {
|
|
83
|
+
const { context: placementContext } = useWebPlacementContext();
|
|
84
|
+
|
|
85
|
+
function resolve(relativePath = "/", options = {}) {
|
|
86
|
+
return resolveShellLinkPath({
|
|
87
|
+
context: placementContext.value,
|
|
88
|
+
surface: String(unref(options.surface ?? surface) || ""),
|
|
89
|
+
explicitTo: options.explicitTo,
|
|
90
|
+
relativePath,
|
|
91
|
+
surfaceRelativePath: options.surfaceRelativePath,
|
|
92
|
+
params: options.params,
|
|
93
|
+
strictParams: options.strictParams !== false
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function toSurface(relativePath = "/", options = {}) {
|
|
98
|
+
return resolve(relativePath, options);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function toWorkspace(relativePath = "/", options = {}) {
|
|
102
|
+
return resolve(relativePath, options);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function toAuto(relativePath = "/", options = {}) {
|
|
106
|
+
return resolve(relativePath, options);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return Object.freeze({
|
|
110
|
+
resolve,
|
|
111
|
+
toSurface,
|
|
112
|
+
toWorkspace,
|
|
113
|
+
toAuto
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export { resolveShellLinkPath, useShellLinkResolver };
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
const DEFAULT_DEBUG_DEPTH = 4;
|
|
2
|
+
|
|
3
|
+
function describeFunction(fn) {
|
|
4
|
+
return `[Function ${fn?.name || "anonymous"}]`;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
function describeSymbol(value) {
|
|
8
|
+
return value?.toString ? value.toString() : "[Symbol]";
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function explodePayload(value, depth = DEFAULT_DEBUG_DEPTH) {
|
|
12
|
+
const visited = new WeakSet();
|
|
13
|
+
|
|
14
|
+
function walk(node, remaining) {
|
|
15
|
+
if (node === null || typeof node !== "object") {
|
|
16
|
+
if (typeof node === "function") {
|
|
17
|
+
return describeFunction(node);
|
|
18
|
+
}
|
|
19
|
+
if (typeof node === "symbol") {
|
|
20
|
+
return describeSymbol(node);
|
|
21
|
+
}
|
|
22
|
+
return node;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (visited.has(node)) {
|
|
26
|
+
return "[Circular]";
|
|
27
|
+
}
|
|
28
|
+
visited.add(node);
|
|
29
|
+
|
|
30
|
+
if (remaining <= 0) {
|
|
31
|
+
return Array.isArray(node) ? "[Array]" : "[Object]";
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (Array.isArray(node)) {
|
|
35
|
+
return node.map((entry) => walk(entry, remaining - 1));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (node instanceof Date) {
|
|
39
|
+
return node.toISOString();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const next = {};
|
|
43
|
+
for (const [key, child] of Object.entries(node)) {
|
|
44
|
+
next[key] = walk(child, remaining - 1);
|
|
45
|
+
}
|
|
46
|
+
return next;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return walk(value, depth);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export { DEFAULT_DEBUG_DEPTH, explodePayload };
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export {
|
|
2
|
+
WEB_PLACEMENT_RUNTIME_CLIENT_TOKEN
|
|
3
|
+
} from "./tokens.js";
|
|
4
|
+
|
|
5
|
+
export {
|
|
6
|
+
createPlacementRegistry
|
|
7
|
+
} from "./registry.js";
|
|
8
|
+
|
|
9
|
+
export {
|
|
10
|
+
useWebPlacementContext
|
|
11
|
+
} from "./inject.js";
|
|
12
|
+
|
|
13
|
+
export {
|
|
14
|
+
resolveRuntimePathname
|
|
15
|
+
} from "./pathname.js";
|
|
16
|
+
|
|
17
|
+
export {
|
|
18
|
+
readPlacementSurfaceConfig,
|
|
19
|
+
resolveSurfaceDefinitionFromPlacementContext,
|
|
20
|
+
joinSurfacePath,
|
|
21
|
+
resolveSurfaceIdFromPlacementPathname,
|
|
22
|
+
resolveSurfaceRootPathFromPlacementContext,
|
|
23
|
+
resolveSurfacePathFromPlacementContext,
|
|
24
|
+
normalizeSurfaceOrigin,
|
|
25
|
+
resolveSurfaceNavigationTargetFromPlacementContext
|
|
26
|
+
} from "./surfaceContext.js";
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import {
|
|
2
|
+
inject,
|
|
3
|
+
onBeforeUnmount,
|
|
4
|
+
onMounted,
|
|
5
|
+
shallowRef
|
|
6
|
+
} from "vue";
|
|
7
|
+
import { WEB_PLACEMENT_RUNTIME_INJECTION_KEY } from "./tokens.js";
|
|
8
|
+
|
|
9
|
+
const EMPTY_WEB_PLACEMENT_RUNTIME = Object.freeze({
|
|
10
|
+
getContext() {
|
|
11
|
+
return Object.freeze({});
|
|
12
|
+
},
|
|
13
|
+
getPlacements() {
|
|
14
|
+
return Object.freeze([]);
|
|
15
|
+
},
|
|
16
|
+
setContext() {
|
|
17
|
+
return Object.freeze({});
|
|
18
|
+
},
|
|
19
|
+
subscribe() {
|
|
20
|
+
return () => {};
|
|
21
|
+
},
|
|
22
|
+
getRevision() {
|
|
23
|
+
return 0;
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
const EMPTY_WEB_PLACEMENT_CONTEXT_REF = shallowRef(Object.freeze({}));
|
|
28
|
+
|
|
29
|
+
const EMPTY_WEB_PLACEMENT_CONTEXT = Object.freeze({
|
|
30
|
+
context: EMPTY_WEB_PLACEMENT_CONTEXT_REF,
|
|
31
|
+
mergeContext() {
|
|
32
|
+
return EMPTY_WEB_PLACEMENT_CONTEXT_REF.value;
|
|
33
|
+
},
|
|
34
|
+
replaceContext() {
|
|
35
|
+
return EMPTY_WEB_PLACEMENT_CONTEXT_REF.value;
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
function useWebPlacementRuntime({ required = false } = {}) {
|
|
40
|
+
const runtime = inject(WEB_PLACEMENT_RUNTIME_INJECTION_KEY, null);
|
|
41
|
+
if (runtime && typeof runtime.getPlacements === "function") {
|
|
42
|
+
return runtime;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (required) {
|
|
46
|
+
throw new Error("Web placement runtime is not available in Vue injection context.");
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return EMPTY_WEB_PLACEMENT_RUNTIME;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function useWebPlacementContext({ required = false } = {}) {
|
|
53
|
+
const runtime = useWebPlacementRuntime({ required });
|
|
54
|
+
if (runtime === EMPTY_WEB_PLACEMENT_RUNTIME) {
|
|
55
|
+
return EMPTY_WEB_PLACEMENT_CONTEXT;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const context = shallowRef(runtime.getContext());
|
|
59
|
+
let unsubscribe = null;
|
|
60
|
+
|
|
61
|
+
onMounted(() => {
|
|
62
|
+
if (typeof runtime.subscribe !== "function") {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
unsubscribe = runtime.subscribe(() => {
|
|
66
|
+
context.value = runtime.getContext();
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
onBeforeUnmount(() => {
|
|
71
|
+
if (typeof unsubscribe === "function") {
|
|
72
|
+
unsubscribe();
|
|
73
|
+
unsubscribe = null;
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
function mergeContext(value = {}, source = "component") {
|
|
78
|
+
context.value = runtime.setContext(value, {
|
|
79
|
+
source
|
|
80
|
+
});
|
|
81
|
+
return context.value;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function replaceContext(value = {}, source = "component") {
|
|
85
|
+
context.value = runtime.setContext(value, {
|
|
86
|
+
replace: true,
|
|
87
|
+
source
|
|
88
|
+
});
|
|
89
|
+
return context.value;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return Object.freeze({
|
|
93
|
+
context,
|
|
94
|
+
mergeContext,
|
|
95
|
+
replaceContext
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export {
|
|
100
|
+
EMPTY_WEB_PLACEMENT_RUNTIME,
|
|
101
|
+
EMPTY_WEB_PLACEMENT_CONTEXT,
|
|
102
|
+
useWebPlacementRuntime,
|
|
103
|
+
useWebPlacementContext
|
|
104
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
function resolveRuntimePathname(candidate = "") {
|
|
2
|
+
const explicitPathname = String(candidate || "").trim();
|
|
3
|
+
if (explicitPathname) {
|
|
4
|
+
return explicitPathname;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
if (typeof window === "object" && window?.location?.pathname) {
|
|
8
|
+
return String(window.location.pathname || "").trim() || "/";
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
return "/";
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export { resolveRuntimePathname };
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import {
|
|
2
|
+
definePlacement
|
|
3
|
+
} from "./validators.js";
|
|
4
|
+
|
|
5
|
+
function createPlacementRegistry({ entries = [] } = {}) {
|
|
6
|
+
const placements = [];
|
|
7
|
+
const ids = new Set();
|
|
8
|
+
|
|
9
|
+
function addPlacement(value = {}) {
|
|
10
|
+
const placement = definePlacement(value);
|
|
11
|
+
if (ids.has(placement.id)) {
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
ids.add(placement.id);
|
|
16
|
+
placements.push(placement);
|
|
17
|
+
return true;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
for (const entry of Array.isArray(entries) ? entries : []) {
|
|
21
|
+
addPlacement(entry);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function hasPlacement(id) {
|
|
25
|
+
return ids.has(String(id || "").trim());
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function build() {
|
|
29
|
+
return Object.freeze([...placements]);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return Object.freeze({
|
|
33
|
+
addPlacement,
|
|
34
|
+
hasPlacement,
|
|
35
|
+
build
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export {
|
|
40
|
+
createPlacementRegistry
|
|
41
|
+
};
|