@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.
Files changed (41) hide show
  1. package/package.descriptor.mjs +165 -0
  2. package/package.json +23 -0
  3. package/src/client/components/ShellErrorHost.vue +208 -0
  4. package/src/client/components/ShellLayout.vue +191 -0
  5. package/src/client/components/ShellOutlet.vue +95 -0
  6. package/src/client/components/useShellLayout.js +93 -0
  7. package/src/client/error/index.js +2 -0
  8. package/src/client/error/inject.js +142 -0
  9. package/src/client/error/normalize.js +75 -0
  10. package/src/client/error/policy.js +50 -0
  11. package/src/client/error/presenters.js +89 -0
  12. package/src/client/error/runtime.js +418 -0
  13. package/src/client/error/store.js +176 -0
  14. package/src/client/error/tokens.js +14 -0
  15. package/src/client/index.js +17 -0
  16. package/src/client/navigation/linkResolver.js +117 -0
  17. package/src/client/placement/debug.js +52 -0
  18. package/src/client/placement/index.js +26 -0
  19. package/src/client/placement/inject.js +104 -0
  20. package/src/client/placement/pathname.js +14 -0
  21. package/src/client/placement/registry.js +41 -0
  22. package/src/client/placement/runtime.js +435 -0
  23. package/src/client/placement/surfaceContext.js +290 -0
  24. package/src/client/placement/tokens.js +29 -0
  25. package/src/client/placement/validators.js +210 -0
  26. package/src/client/providers/ShellWebClientProvider.js +352 -0
  27. package/templates/src/App.vue +11 -0
  28. package/templates/src/components/ShellLayout.vue +247 -0
  29. package/templates/src/error.js +13 -0
  30. package/templates/src/pages/console/index.vue +24 -0
  31. package/templates/src/pages/console.vue +20 -0
  32. package/templates/src/pages/home/index.vue +54 -0
  33. package/templates/src/pages/home.vue +20 -0
  34. package/templates/src/placement.js +12 -0
  35. package/test/errorRuntime.test.js +191 -0
  36. package/test/errorStore.test.js +26 -0
  37. package/test/linkResolver.test.js +112 -0
  38. package/test/placementRegistry.test.js +45 -0
  39. package/test/placementRuntime.test.js +374 -0
  40. package/test/provider.test.js +163 -0
  41. 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
+ };