@jskit-ai/shell-web 0.1.250 → 0.1.252
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/fixtures/adaptive-shell/src/main.js +6 -3
- package/package.json +2 -2
- package/patterns/application-shell/PATTERN.md +18 -4
- package/patterns/application-shell/example/src/pages/home/index.vue +15 -5
- package/patterns/application-shell/example/tests/e2e/adaptive-shell.spec.ts +1 -1
- package/src/architecture/inspectVueArchitecture.js +1092 -336
- package/src/client/asyncModuleRecovery/runtime.js +116 -0
- package/src/client/components/ShellLayout.vue +18 -288
- package/src/client/components/ShellMenuLinkItem.vue +3 -21
- package/src/client/components/ShellOutlet.vue +2 -24
- package/src/client/components/ShellRouteTransition.vue +16 -114
- package/src/client/components/ShellSurfaceAwareMenuLinkItem.vue +5 -100
- package/src/client/components/ShellTabLinkItem.vue +5 -101
- package/src/client/composables/useShellNavigationLink.js +85 -0
- package/src/client/composables/useShellPullToRefresh.js +260 -0
- package/src/client/error/index.js +1 -1
- package/src/client/error/inject.js +4 -1
- package/src/client/error/policy.js +4 -13
- package/src/client/error/presenters.js +4 -9
- package/src/client/error/store.js +1 -1
- package/src/client/navigation/linkResolver.js +5 -27
- package/src/client/navigation/usePaths.js +1 -21
- package/src/client/placement/index.js +2 -0
- package/src/client/placement/inject.js +4 -1
- package/src/client/placement/runtime.js +2 -4
- package/src/client/placement/surfaceContext.js +11 -8
- package/src/client/placement/validators.js +18 -65
- package/src/client/providers/ShellWebClientProvider.js +100 -356
- package/src/client/requestRecovery/runtime.js +29 -77
- package/src/client/runtime/bootstrapRuntime.js +5 -4
- package/src/client/runtime/refreshRuntime.js +122 -0
- package/src/client/support/gestureTargets.js +26 -0
- package/src/client/support/menuLinkTarget.js +35 -19
- package/src/client/support/routeParams.js +26 -0
- package/src/client/support/routeSwipeGesture.js +88 -0
- package/src/client/support/routeTransitionKey.js +1 -3
- package/src/client/support/shellLayoutClass.js +32 -0
- package/test/bootstrapRuntime.test.js +98 -0
- package/test/errorActions.test.js +2 -1
- package/test/errorRuntime.test.js +30 -0
- package/test/linkResolver.test.js +23 -1
- package/test/menuLinkTarget.test.js +77 -0
- package/test/routeSwipeGesture.test.js +49 -0
- package/test/settingsPlacementContract.test.js +16 -6
- package/test/shellGestureSupport.test.js +46 -0
- package/test/surfaceContext.test.js +53 -0
- package/test/useShellPullToRefresh.test.js +123 -0
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { isRecord, normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
|
|
2
2
|
import {
|
|
3
|
+
WEB_PLACEMENT_SURFACE_ANY,
|
|
3
4
|
normalizePlacementKind,
|
|
5
|
+
normalizePlacementSurfaceId,
|
|
6
|
+
normalizePlacementSurfaces as normalizeKernelPlacementSurfaces,
|
|
4
7
|
normalizePlacementTopologyDefinition,
|
|
5
|
-
normalizeSemanticPlacementId,
|
|
6
8
|
resolvePlacementTargetReference
|
|
7
9
|
} from "@jskit-ai/kernel/shared/support/shellLayoutTargets";
|
|
8
10
|
|
|
9
|
-
const WEB_PLACEMENT_SURFACE_ANY = "*";
|
|
10
|
-
|
|
11
11
|
function isRenderableComponent(value) {
|
|
12
12
|
if (typeof value === "function") {
|
|
13
13
|
return true;
|
|
@@ -16,41 +16,16 @@ function isRenderableComponent(value) {
|
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
function normalizeSurface(value) {
|
|
19
|
-
|
|
20
|
-
if (!normalized) {
|
|
21
|
-
return WEB_PLACEMENT_SURFACE_ANY;
|
|
22
|
-
}
|
|
23
|
-
if (normalized === WEB_PLACEMENT_SURFACE_ANY) {
|
|
24
|
-
return WEB_PLACEMENT_SURFACE_ANY;
|
|
25
|
-
}
|
|
26
|
-
return normalized;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function isValidSurfaceIdToken(value = "") {
|
|
30
|
-
return /^[a-z0-9]+(?:[._-][a-z0-9]+)*$/.test(value);
|
|
19
|
+
return normalizeText(value).toLowerCase() || WEB_PLACEMENT_SURFACE_ANY;
|
|
31
20
|
}
|
|
32
21
|
|
|
33
22
|
function normalizePlacementSurface(value, { strict = false, source = "placement" } = {}) {
|
|
34
|
-
const
|
|
35
|
-
if (!
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
}
|
|
39
|
-
return "";
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
if (normalized === WEB_PLACEMENT_SURFACE_ANY) {
|
|
43
|
-
return WEB_PLACEMENT_SURFACE_ANY;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
if (isValidSurfaceIdToken(normalized)) {
|
|
47
|
-
return normalized;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
if (strict) {
|
|
51
|
-
throw new TypeError(`${source} surface "${normalized}" is invalid.`);
|
|
23
|
+
const surface = normalizePlacementSurfaceId(value);
|
|
24
|
+
if (!surface && strict) {
|
|
25
|
+
const normalized = normalizeText(value).toLowerCase();
|
|
26
|
+
throw new TypeError(normalized ? `${source} surface "${normalized}" is invalid.` : `${source} requires surface id or "*".`);
|
|
52
27
|
}
|
|
53
|
-
return
|
|
28
|
+
return surface;
|
|
54
29
|
}
|
|
55
30
|
|
|
56
31
|
function toInteger(value, fallback = 1000) {
|
|
@@ -74,36 +49,16 @@ function normalizePlacementTarget(value, { strict = false, source = "placement"
|
|
|
74
49
|
}
|
|
75
50
|
|
|
76
51
|
function normalizePlacementSurfaces(value, { strict = false, source = "placement" } = {}) {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
for (const candidate of candidates) {
|
|
85
|
-
const surface = normalizePlacementSurface(candidate, {
|
|
86
|
-
strict,
|
|
87
|
-
source
|
|
88
|
-
});
|
|
89
|
-
if (!surface || seen.has(surface)) {
|
|
90
|
-
continue;
|
|
91
|
-
}
|
|
92
|
-
if (surface === WEB_PLACEMENT_SURFACE_ANY) {
|
|
93
|
-
return Object.freeze([WEB_PLACEMENT_SURFACE_ANY]);
|
|
94
|
-
}
|
|
95
|
-
seen.add(surface);
|
|
96
|
-
normalized.push(surface);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
if (normalized.length < 1) {
|
|
100
|
-
if (strict) {
|
|
101
|
-
throw new TypeError(`${source} requires at least one valid surface id.`);
|
|
52
|
+
if (strict) {
|
|
53
|
+
// Strict callers fail on the first invalid surface the kernel would otherwise drop; "*" ends the list.
|
|
54
|
+
const candidates = Array.isArray(value) ? value : value === undefined || value === null ? [] : [value];
|
|
55
|
+
for (const candidate of candidates) {
|
|
56
|
+
if (normalizePlacementSurface(candidate, { strict, source }) === WEB_PLACEMENT_SURFACE_ANY) {
|
|
57
|
+
break;
|
|
58
|
+
}
|
|
102
59
|
}
|
|
103
|
-
return Object.freeze([WEB_PLACEMENT_SURFACE_ANY]);
|
|
104
60
|
}
|
|
105
|
-
|
|
106
|
-
return Object.freeze(normalized);
|
|
61
|
+
return normalizeKernelPlacementSurfaces(value);
|
|
107
62
|
}
|
|
108
63
|
|
|
109
64
|
function normalizePlacementDefinition(value, { strict = false, source = "placement" } = {}) {
|
|
@@ -186,7 +141,6 @@ function definePlacementTopology(value = {}) {
|
|
|
186
141
|
}
|
|
187
142
|
|
|
188
143
|
export {
|
|
189
|
-
isRecord,
|
|
190
144
|
isRenderableComponent,
|
|
191
145
|
normalizeSurface,
|
|
192
146
|
normalizePlacementSurface,
|
|
@@ -194,6 +148,5 @@ export {
|
|
|
194
148
|
normalizePlacementSurfaces,
|
|
195
149
|
normalizePlacementDefinition,
|
|
196
150
|
definePlacement,
|
|
197
|
-
definePlacementTopology
|
|
198
|
-
normalizeSemanticPlacementId
|
|
151
|
+
definePlacementTopology
|
|
199
152
|
};
|
|
@@ -1,12 +1,6 @@
|
|
|
1
1
|
import { getClientAppConfig } from "@jskit-ai/kernel/client";
|
|
2
2
|
import { defineProvider } from "@jskit-ai/kernel/shared/capabilities";
|
|
3
|
-
import {
|
|
4
|
-
createAsyncModuleRecoveryState,
|
|
5
|
-
guardedReloadApp,
|
|
6
|
-
installAsyncModuleRecoveryHandlers,
|
|
7
|
-
isDynamicImportError,
|
|
8
|
-
notifyAsyncModuleLoadError
|
|
9
|
-
} from "@jskit-ai/kernel/client/asyncModuleRecovery";
|
|
3
|
+
import { isDynamicImportError } from "@jskit-ai/kernel/client/asyncModuleRecovery";
|
|
10
4
|
import {
|
|
11
5
|
isMissingDynamicModule,
|
|
12
6
|
notifyDynamicImportFailure
|
|
@@ -14,6 +8,7 @@ import {
|
|
|
14
8
|
import {
|
|
15
9
|
SHELL_ASYNC_MODULE_RECOVERY_RUNTIME_KEY
|
|
16
10
|
} from "../asyncModuleRecovery/inject.js";
|
|
11
|
+
import { createShellAsyncModuleRecoveryRuntime } from "../asyncModuleRecovery/runtime.js";
|
|
17
12
|
import {
|
|
18
13
|
SHELL_REQUEST_RECOVERY_RUNTIME_KEY
|
|
19
14
|
} from "../requestRecovery/inject.js";
|
|
@@ -33,85 +28,104 @@ import {
|
|
|
33
28
|
createMaterialDialogPresenter,
|
|
34
29
|
createMaterialSnackbarPresenter
|
|
35
30
|
} from "../error/presenters.js";
|
|
31
|
+
import { SHELL_WEB_ERROR_RUNTIME_KEY } from "../error/inject.js";
|
|
36
32
|
import {
|
|
37
33
|
createErrorRuntime
|
|
38
34
|
} from "../error/runtime.js";
|
|
39
35
|
import {
|
|
40
36
|
createErrorPresentationStore
|
|
41
37
|
} from "../error/store.js";
|
|
38
|
+
import { SHELL_WEB_PLACEMENT_RUNTIME_KEY } from "../placement/inject.js";
|
|
42
39
|
import { createWebPlacementRuntime } from "../placement/runtime.js";
|
|
43
40
|
import { useShellErrorPresentationStore } from "../stores/useShellErrorPresentationStore.js";
|
|
44
41
|
import { buildSurfaceConfigContext } from "../placement/surfaceContext.js";
|
|
45
42
|
import { createShellBootstrapRuntime } from "../runtime/bootstrapRuntime.js";
|
|
43
|
+
import {
|
|
44
|
+
SHELL_WEB_REFRESH_RUNTIME_KEY,
|
|
45
|
+
createShellRefreshRuntime
|
|
46
|
+
} from "../runtime/refreshRuntime.js";
|
|
46
47
|
import { createBootstrapPayloadHandlerRegistry } from "../bootstrap/bootstrapPayloadHandlerRegistry.js";
|
|
47
48
|
import { resolveBootstrapErrorStatusCode } from "../bootstrap/bootstrapErrorStatus.js";
|
|
48
49
|
|
|
49
|
-
// Keep
|
|
50
|
+
// Keep these constants for diagnostics, but keep each import() below as a literal string so Vite can statically analyze it.
|
|
50
51
|
const APP_PLACEMENT_MODULE_SPECIFIER = "/src/placement.js";
|
|
51
52
|
const APP_PLACEMENT_TOPOLOGY_MODULE_SPECIFIER = "/src/placementTopology.js";
|
|
52
53
|
const APP_ERROR_MODULE_SPECIFIER = "/src/error.js";
|
|
53
|
-
const ASYNC_MODULE_RELOAD_FAILURE_MESSAGE =
|
|
54
|
-
"The app cannot reload because the app server is not reachable. Restart the server, then click Reload.";
|
|
55
54
|
|
|
56
|
-
|
|
55
|
+
// Loads an optional app module's default export. `resolve` returns the usable value, or undefined when the
|
|
56
|
+
// export has the wrong shape. A missing module silently yields `fallback`; any other failure is reported.
|
|
57
|
+
async function loadAppModule({
|
|
58
|
+
importModule,
|
|
59
|
+
specifier,
|
|
60
|
+
label,
|
|
61
|
+
resolve,
|
|
62
|
+
invalidExportMessage = "",
|
|
63
|
+
loadFailureMessage,
|
|
64
|
+
fallback,
|
|
65
|
+
logger,
|
|
66
|
+
asyncModuleRecoveryRuntime = null
|
|
67
|
+
}) {
|
|
57
68
|
try {
|
|
58
|
-
const moduleNamespace = await
|
|
69
|
+
const moduleNamespace = await importModule();
|
|
59
70
|
const exported = moduleNamespace?.default;
|
|
60
|
-
const resolved =
|
|
61
|
-
if (
|
|
71
|
+
const resolved = await resolve(exported);
|
|
72
|
+
if (resolved !== undefined) {
|
|
62
73
|
return resolved;
|
|
63
74
|
}
|
|
64
75
|
|
|
65
76
|
logger.warn(
|
|
66
77
|
{
|
|
67
|
-
module:
|
|
78
|
+
module: specifier,
|
|
68
79
|
exportedType: typeof exported
|
|
69
80
|
},
|
|
70
|
-
|
|
81
|
+
invalidExportMessage
|
|
71
82
|
);
|
|
72
83
|
} catch (error) {
|
|
73
|
-
if (isMissingDynamicModule(error,
|
|
74
|
-
return
|
|
84
|
+
if (isMissingDynamicModule(error, specifier)) {
|
|
85
|
+
return fallback;
|
|
75
86
|
}
|
|
76
|
-
notifyDynamicImportFailure(asyncModuleRecoveryRuntime, error, {
|
|
77
|
-
label: "Placement config"
|
|
78
|
-
});
|
|
87
|
+
notifyDynamicImportFailure(asyncModuleRecoveryRuntime, error, { label });
|
|
79
88
|
|
|
80
89
|
logger.warn(
|
|
81
90
|
{
|
|
82
|
-
module:
|
|
91
|
+
module: specifier,
|
|
83
92
|
error: String(error?.message || error || "unknown error")
|
|
84
93
|
},
|
|
85
|
-
|
|
94
|
+
loadFailureMessage
|
|
86
95
|
);
|
|
87
96
|
}
|
|
88
97
|
|
|
89
|
-
return
|
|
98
|
+
return fallback;
|
|
90
99
|
}
|
|
91
100
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
return
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
error: String(error?.message || error || "unknown error")
|
|
109
|
-
},
|
|
110
|
-
"Failed to load app placement topology module; using empty topology."
|
|
111
|
-
);
|
|
112
|
-
}
|
|
101
|
+
function loadAppPlacementDefinitions(logger, asyncModuleRecoveryRuntime = null) {
|
|
102
|
+
return loadAppModule({
|
|
103
|
+
importModule: () => import("/src/placement.js"),
|
|
104
|
+
specifier: APP_PLACEMENT_MODULE_SPECIFIER,
|
|
105
|
+
label: "Placement config",
|
|
106
|
+
resolve(exported) {
|
|
107
|
+
const resolved = typeof exported === "function" ? exported() : exported;
|
|
108
|
+
return Array.isArray(resolved) ? resolved : undefined;
|
|
109
|
+
},
|
|
110
|
+
invalidExportMessage: "App placement module default export did not resolve to an array; using empty list.",
|
|
111
|
+
loadFailureMessage: "Failed to load app placement module; using empty list.",
|
|
112
|
+
fallback: [],
|
|
113
|
+
logger,
|
|
114
|
+
asyncModuleRecoveryRuntime
|
|
115
|
+
});
|
|
116
|
+
}
|
|
113
117
|
|
|
114
|
-
|
|
118
|
+
function loadAppPlacementTopology(logger, asyncModuleRecoveryRuntime = null) {
|
|
119
|
+
return loadAppModule({
|
|
120
|
+
importModule: () => import("/src/placementTopology.js"),
|
|
121
|
+
specifier: APP_PLACEMENT_TOPOLOGY_MODULE_SPECIFIER,
|
|
122
|
+
label: "Placement topology",
|
|
123
|
+
resolve: (exported) => resolveAppPlacementTopologyExport(exported, logger),
|
|
124
|
+
loadFailureMessage: "Failed to load app placement topology module; using empty topology.",
|
|
125
|
+
fallback: [],
|
|
126
|
+
logger,
|
|
127
|
+
asyncModuleRecoveryRuntime
|
|
128
|
+
});
|
|
115
129
|
}
|
|
116
130
|
|
|
117
131
|
function resolveAppPlacementTopologyExport(exported, logger) {
|
|
@@ -146,178 +160,21 @@ function createErrorConfigToolkit(errorRuntime) {
|
|
|
146
160
|
});
|
|
147
161
|
}
|
|
148
162
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
return resolved;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
},
|
|
165
|
-
"App error module default export did not resolve to an object; using shell-web defaults."
|
|
166
|
-
);
|
|
167
|
-
} catch (error) {
|
|
168
|
-
if (isMissingDynamicModule(error, APP_ERROR_MODULE_SPECIFIER)) {
|
|
169
|
-
return {};
|
|
170
|
-
}
|
|
171
|
-
notifyDynamicImportFailure(asyncModuleRecoveryRuntime, error, {
|
|
172
|
-
label: "Error config"
|
|
173
|
-
});
|
|
174
|
-
|
|
175
|
-
logger.warn(
|
|
176
|
-
{
|
|
177
|
-
module: APP_ERROR_MODULE_SPECIFIER,
|
|
178
|
-
error: String(error?.message || error || "unknown error")
|
|
179
|
-
},
|
|
180
|
-
"Failed to load app error module; using shell-web defaults."
|
|
181
|
-
);
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
return {};
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
function applyAppErrorConfig(errorRuntime, errorConfig = {}) {
|
|
188
|
-
const source = isRecord(errorConfig) ? errorConfig : {};
|
|
189
|
-
const configPayload = {};
|
|
190
|
-
|
|
191
|
-
if (Array.isArray(source.presenters) && source.presenters.length > 0) {
|
|
192
|
-
configPayload.presenters = source.presenters;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
if (Object.prototype.hasOwnProperty.call(source, "policy")) {
|
|
196
|
-
configPayload.policy = source.policy;
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
if (Object.prototype.hasOwnProperty.call(source, "defaultPresenterId")) {
|
|
200
|
-
configPayload.defaultPresenterId = source.defaultPresenterId;
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
if (Object.keys(configPayload).length > 0) {
|
|
204
|
-
errorRuntime.configure(configPayload);
|
|
205
|
-
return;
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
errorRuntime.assertBootReady();
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
function isPullRefreshQuery(query = null) {
|
|
212
|
-
const meta = isRecord(query?.meta) ? query.meta : {};
|
|
213
|
-
if (meta.jskitRefresh === "pull") {
|
|
214
|
-
return true;
|
|
215
|
-
}
|
|
216
|
-
if (meta.jskitRefresh === false) {
|
|
217
|
-
return false;
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
const jskitMeta = isRecord(meta.jskit) ? meta.jskit : {};
|
|
221
|
-
return jskitMeta.refreshOnPull !== false;
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
function createShellRefreshRuntime({
|
|
225
|
-
bootstrapRuntime = null,
|
|
226
|
-
queryClient = null,
|
|
227
|
-
errorRuntime = null,
|
|
228
|
-
logger = null
|
|
229
|
-
} = {}) {
|
|
230
|
-
const runtimeLogger = createSharedProviderLogger(logger);
|
|
231
|
-
let refreshQueue = Promise.resolve(null);
|
|
232
|
-
|
|
233
|
-
async function refreshBootstrap(reason) {
|
|
234
|
-
if (!bootstrapRuntime || typeof bootstrapRuntime.refresh !== "function") {
|
|
235
|
-
return false;
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
await bootstrapRuntime.refresh(reason);
|
|
239
|
-
return true;
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
async function refetchPullQueries() {
|
|
243
|
-
if (!queryClient || typeof queryClient.refetchQueries !== "function") {
|
|
244
|
-
return false;
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
await queryClient.refetchQueries(
|
|
248
|
-
{
|
|
249
|
-
type: "active",
|
|
250
|
-
predicate: isPullRefreshQuery
|
|
251
|
-
},
|
|
252
|
-
{
|
|
253
|
-
throwOnError: false
|
|
254
|
-
}
|
|
255
|
-
);
|
|
256
|
-
return true;
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
function reportRefreshFailure(error) {
|
|
260
|
-
runtimeLogger.warn(
|
|
261
|
-
{
|
|
262
|
-
error: String(error?.message || error || "unknown error")
|
|
263
|
-
},
|
|
264
|
-
"Shell refresh failed."
|
|
265
|
-
);
|
|
266
|
-
|
|
267
|
-
if (!errorRuntime || typeof errorRuntime.report !== "function") {
|
|
268
|
-
return;
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
errorRuntime.report({
|
|
272
|
-
source: "shell-web.refresh",
|
|
273
|
-
message: "Unable to refresh. Check the connection and try again.",
|
|
274
|
-
intent: "app-recoverable",
|
|
275
|
-
severity: "error",
|
|
276
|
-
dedupeKey: "shell-web.refresh.failed",
|
|
277
|
-
dedupeWindowMs: 2000,
|
|
278
|
-
action: {
|
|
279
|
-
label: "Retry",
|
|
280
|
-
dismissOnRun: true,
|
|
281
|
-
handler() {
|
|
282
|
-
void refresh("retry");
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
});
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
async function performRefresh(reason = "manual") {
|
|
289
|
-
const normalizedReason = String(reason || "manual").trim() || "manual";
|
|
290
|
-
try {
|
|
291
|
-
const [bootstrapRefreshed, queriesRefetched] = await Promise.all([
|
|
292
|
-
refreshBootstrap(normalizedReason),
|
|
293
|
-
refetchPullQueries()
|
|
294
|
-
]);
|
|
295
|
-
|
|
296
|
-
return Object.freeze({
|
|
297
|
-
reason: normalizedReason,
|
|
298
|
-
bootstrapRefreshed,
|
|
299
|
-
queriesRefetched
|
|
300
|
-
});
|
|
301
|
-
} catch (error) {
|
|
302
|
-
reportRefreshFailure(error);
|
|
303
|
-
return Object.freeze({
|
|
304
|
-
reason: normalizedReason,
|
|
305
|
-
bootstrapRefreshed: false,
|
|
306
|
-
queriesRefetched: false,
|
|
307
|
-
error
|
|
308
|
-
});
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
function refresh(reason = "manual") {
|
|
313
|
-
refreshQueue = refreshQueue
|
|
314
|
-
.catch(() => null)
|
|
315
|
-
.then(() => performRefresh(reason));
|
|
316
|
-
return refreshQueue;
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
return Object.freeze({
|
|
320
|
-
refresh
|
|
163
|
+
function loadAppErrorConfig(logger, errorRuntime, asyncModuleRecoveryRuntime = null) {
|
|
164
|
+
return loadAppModule({
|
|
165
|
+
importModule: () => import("/src/error.js"),
|
|
166
|
+
specifier: APP_ERROR_MODULE_SPECIFIER,
|
|
167
|
+
label: "Error config",
|
|
168
|
+
async resolve(exported) {
|
|
169
|
+
const toolkit = createErrorConfigToolkit(errorRuntime);
|
|
170
|
+
const resolved = typeof exported === "function" ? await exported(toolkit) : exported;
|
|
171
|
+
return isRecord(resolved) ? resolved : undefined;
|
|
172
|
+
},
|
|
173
|
+
invalidExportMessage: "App error module default export did not resolve to an object; using shell-web defaults.",
|
|
174
|
+
loadFailureMessage: "Failed to load app error module; using shell-web defaults.",
|
|
175
|
+
fallback: {},
|
|
176
|
+
logger,
|
|
177
|
+
asyncModuleRecoveryRuntime
|
|
321
178
|
});
|
|
322
179
|
}
|
|
323
180
|
|
|
@@ -388,109 +245,6 @@ function installRouterErrorBridge(router, errorRuntime, logger) {
|
|
|
388
245
|
});
|
|
389
246
|
}
|
|
390
247
|
|
|
391
|
-
function createShellAsyncModuleRecoveryRuntime({
|
|
392
|
-
router = null,
|
|
393
|
-
errorRuntime = null,
|
|
394
|
-
logger = null
|
|
395
|
-
} = {}) {
|
|
396
|
-
const runtimeLogger = createSharedProviderLogger(logger);
|
|
397
|
-
const state = createAsyncModuleRecoveryState();
|
|
398
|
-
let installedRecovery = null;
|
|
399
|
-
|
|
400
|
-
function report(nextState = state) {
|
|
401
|
-
if (!errorRuntime || typeof errorRuntime.report !== "function") {
|
|
402
|
-
runtimeLogger.warn(
|
|
403
|
-
{
|
|
404
|
-
label: String(nextState?.label || "")
|
|
405
|
-
},
|
|
406
|
-
"Async module recovery could not report because the shell error runtime is unavailable."
|
|
407
|
-
);
|
|
408
|
-
return null;
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
try {
|
|
412
|
-
return errorRuntime.report({
|
|
413
|
-
source: "shell-web.async-module-recovery",
|
|
414
|
-
message: String(nextState?.message || "A required app module could not load."),
|
|
415
|
-
cause: nextState?.error || null,
|
|
416
|
-
intent: "app-recoverable",
|
|
417
|
-
severity: "warning",
|
|
418
|
-
dedupeKey: `shell-web.async-module-recovery.${Number(nextState?.attempt || 0)}`,
|
|
419
|
-
action: {
|
|
420
|
-
label: "Reload",
|
|
421
|
-
dismissOnRun: true,
|
|
422
|
-
handler() {
|
|
423
|
-
return reload();
|
|
424
|
-
}
|
|
425
|
-
}
|
|
426
|
-
});
|
|
427
|
-
} catch (error) {
|
|
428
|
-
runtimeLogger.error(
|
|
429
|
-
{
|
|
430
|
-
label: String(nextState?.label || ""),
|
|
431
|
-
error: String(error?.message || error || "unknown error")
|
|
432
|
-
},
|
|
433
|
-
"Async module recovery could not report through the shell error runtime."
|
|
434
|
-
);
|
|
435
|
-
return null;
|
|
436
|
-
}
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
async function reload() {
|
|
440
|
-
const reloaded = await guardedReloadApp({
|
|
441
|
-
state,
|
|
442
|
-
label: "App",
|
|
443
|
-
message: ASYNC_MODULE_RELOAD_FAILURE_MESSAGE
|
|
444
|
-
});
|
|
445
|
-
if (!reloaded) {
|
|
446
|
-
report(state);
|
|
447
|
-
}
|
|
448
|
-
return reloaded;
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
function notify(error = null, {
|
|
452
|
-
label = "App module",
|
|
453
|
-
message = "",
|
|
454
|
-
stale = isDynamicImportError(error)
|
|
455
|
-
} = {}) {
|
|
456
|
-
notifyAsyncModuleLoadError(state, error, {
|
|
457
|
-
label,
|
|
458
|
-
message,
|
|
459
|
-
retry: state.retry,
|
|
460
|
-
stale
|
|
461
|
-
});
|
|
462
|
-
return report(state);
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
function install() {
|
|
466
|
-
if (installedRecovery) {
|
|
467
|
-
return installedRecovery;
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
installedRecovery = installAsyncModuleRecoveryHandlers({
|
|
471
|
-
state,
|
|
472
|
-
label: "App module",
|
|
473
|
-
router,
|
|
474
|
-
onNotify: report
|
|
475
|
-
});
|
|
476
|
-
return installedRecovery;
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
function dispose() {
|
|
480
|
-
installedRecovery?.dispose?.();
|
|
481
|
-
installedRecovery = null;
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
return Object.freeze({
|
|
485
|
-
dispose,
|
|
486
|
-
install,
|
|
487
|
-
notify,
|
|
488
|
-
reload,
|
|
489
|
-
report,
|
|
490
|
-
state
|
|
491
|
-
});
|
|
492
|
-
}
|
|
493
|
-
|
|
494
248
|
const ShellWebClientProvider = defineProvider({
|
|
495
249
|
id: "shell.web.client",
|
|
496
250
|
requires: {
|
|
@@ -588,39 +342,33 @@ const ShellWebClientProvider = defineProvider({
|
|
|
588
342
|
} = outputs.shell;
|
|
589
343
|
asyncModuleRecoveryRuntime.install();
|
|
590
344
|
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
345
|
+
const placementTopology = await loadAppPlacementTopology(logger, asyncModuleRecoveryRuntime);
|
|
346
|
+
placementRuntime.replacePlacementTopology(placementTopology, { source: APP_PLACEMENT_TOPOLOGY_MODULE_SPECIFIER });
|
|
347
|
+
const placements = await loadAppPlacementDefinitions(logger, asyncModuleRecoveryRuntime);
|
|
348
|
+
placementRuntime.replacePlacements(placements, { source: APP_PLACEMENT_MODULE_SPECIFIER });
|
|
349
|
+
const appConfig = getClientAppConfig();
|
|
350
|
+
const surfaceConfig = buildSurfaceConfigContext(surfaceRuntime, {
|
|
351
|
+
tenancyMode: appConfig?.tenancyMode
|
|
352
|
+
});
|
|
353
|
+
const surfaceAccessPolicies =
|
|
354
|
+
appConfig?.surfaceAccessPolicies && typeof appConfig.surfaceAccessPolicies === "object"
|
|
355
|
+
? appConfig.surfaceAccessPolicies
|
|
356
|
+
: {};
|
|
357
|
+
placementRuntime.setContext(
|
|
358
|
+
{
|
|
359
|
+
surfaceConfig,
|
|
360
|
+
surfaceAccessPolicies
|
|
361
|
+
},
|
|
362
|
+
{
|
|
363
|
+
source: "shell-web.surface-config"
|
|
595
364
|
}
|
|
596
|
-
|
|
597
|
-
placementRuntime.replacePlacements(placements, { source: APP_PLACEMENT_MODULE_SPECIFIER });
|
|
598
|
-
const appConfig = getClientAppConfig();
|
|
599
|
-
const surfaceConfig = buildSurfaceConfigContext(surfaceRuntime, {
|
|
600
|
-
tenancyMode: appConfig?.tenancyMode
|
|
601
|
-
});
|
|
602
|
-
const surfaceAccessPolicies =
|
|
603
|
-
appConfig?.surfaceAccessPolicies && typeof appConfig.surfaceAccessPolicies === "object"
|
|
604
|
-
? appConfig.surfaceAccessPolicies
|
|
605
|
-
: {};
|
|
606
|
-
placementRuntime.setContext(
|
|
607
|
-
{
|
|
608
|
-
surfaceConfig,
|
|
609
|
-
surfaceAccessPolicies
|
|
610
|
-
},
|
|
611
|
-
{
|
|
612
|
-
source: "shell-web.surface-config"
|
|
613
|
-
}
|
|
614
|
-
);
|
|
615
|
-
}
|
|
365
|
+
);
|
|
616
366
|
|
|
617
367
|
const errorConfig = await loadAppErrorConfig(logger, errorRuntime, asyncModuleRecoveryRuntime);
|
|
618
|
-
|
|
368
|
+
errorRuntime.configure(errorConfig);
|
|
619
369
|
requestRecoveryRuntime.install();
|
|
620
370
|
|
|
621
|
-
|
|
622
|
-
await bootstrapRuntime.initialize();
|
|
623
|
-
}
|
|
371
|
+
await bootstrapRuntime.initialize();
|
|
624
372
|
|
|
625
373
|
if (!vueApp || typeof vueApp.provide !== "function" || typeof vueApp.use !== "function") {
|
|
626
374
|
return;
|
|
@@ -630,15 +378,11 @@ const ShellWebClientProvider = defineProvider({
|
|
|
630
378
|
}
|
|
631
379
|
useShellErrorPresentationStore(pinia).attachRuntimeStore(errorPresentationStore);
|
|
632
380
|
|
|
633
|
-
vueApp.provide(
|
|
634
|
-
vueApp.provide(
|
|
635
|
-
vueApp.provide(
|
|
381
|
+
vueApp.provide(SHELL_WEB_PLACEMENT_RUNTIME_KEY, placementRuntime);
|
|
382
|
+
vueApp.provide(SHELL_WEB_REFRESH_RUNTIME_KEY, refreshRuntime);
|
|
383
|
+
vueApp.provide(SHELL_WEB_ERROR_RUNTIME_KEY, errorRuntime);
|
|
636
384
|
vueApp.provide(SHELL_ASYNC_MODULE_RECOVERY_RUNTIME_KEY, asyncModuleRecoveryRuntime);
|
|
637
385
|
vueApp.provide(SHELL_REQUEST_RECOVERY_RUNTIME_KEY, requestRecoveryRuntime);
|
|
638
|
-
vueApp.provide(
|
|
639
|
-
"jskit.shell-web.runtime.web-error.presentation-store.client",
|
|
640
|
-
errorPresentationStore
|
|
641
|
-
);
|
|
642
386
|
|
|
643
387
|
installVueErrorBridge(vueApp, errorRuntime, logger);
|
|
644
388
|
installRouterErrorBridge(router, errorRuntime, logger);
|