@jskit-ai/kernel 0.1.245 → 0.1.247

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 (102) hide show
  1. package/client/appConfig.js +2 -1
  2. package/client/asyncModuleRecovery.js +2 -4
  3. package/client/index.d.ts +13 -75
  4. package/client/index.js +1 -1
  5. package/client/logging.js +8 -2
  6. package/client/moduleBootstrap.js +41 -33
  7. package/client/moduleBootstrap.test.js +35 -0
  8. package/client/shellBootstrap.js +1 -8
  9. package/client/shellBootstrap.test.js +32 -5
  10. package/client/shellRouting.js +16 -61
  11. package/client/shellRouting.test.js +30 -1
  12. package/client/vite/clientBootstrapPlugin.js +0 -6
  13. package/client/vite/clientBootstrapPlugin.test.js +2 -3
  14. package/internal/node/installedPackages.js +19 -62
  15. package/internal/node/installedPackages.test.js +33 -0
  16. package/package.json +2 -4
  17. package/server/actions/actionCatalogue.js +13 -34
  18. package/{shared → server}/actions/actionContributorHelpers.js +0 -5
  19. package/{shared → server}/actions/actionDefinitions.js +24 -131
  20. package/{shared → server}/actions/actionDefinitions.test.js +75 -21
  21. package/server/actions/actionEvents.js +97 -2
  22. package/server/actions/actionEvents.test.js +79 -0
  23. package/{shared → server}/actions/executionContext.js +3 -13
  24. package/server/actions/idempotency.js +24 -0
  25. package/server/actions/pipeline.js +87 -0
  26. package/{shared → server}/actions/policies.js +20 -33
  27. package/{shared → server}/actions/policies.test.js +6 -6
  28. package/{shared → server}/actions/registry.js +3 -22
  29. package/{shared → server}/actions/registry.test.js +70 -32
  30. package/server/actions/requestMeta.js +17 -0
  31. package/server/http/capabilityHttpRuntime.js +1 -1
  32. package/server/http/capabilityHttpRuntime.test.js +56 -0
  33. package/server/http/errorHandling.js +291 -0
  34. package/server/{runtime/fastifyBootstrap.test.js → http/errorHandling.test.js} +72 -120
  35. package/server/http/lib/routeRegistration.js +36 -150
  36. package/server/http/lib/routeTransport.js +2 -0
  37. package/server/http/lib/routeValidator.js +5 -47
  38. package/server/http/lib/routeValidator.test.js +15 -17
  39. package/server/http/lib/router.js +10 -94
  40. package/server/platform/providerRuntime/capabilityProviderLoader.js +2 -1
  41. package/server/platform/providerRuntime/helpers.js +1 -34
  42. package/server/runtime/integers.js +1 -5
  43. package/server/runtime/serviceAuthorization.js +4 -9
  44. package/server/support/appConfig.js +2 -1
  45. package/server/support/appConfigFiles.js +1 -38
  46. package/server/support/appConfigFiles.test.js +3 -39
  47. package/server/support/index.js +1 -20
  48. package/shared/capabilities/defineProvider.js +1 -1
  49. package/shared/capabilities/index.js +1 -1
  50. package/shared/index.js +1 -1
  51. package/shared/support/index.js +1 -4
  52. package/shared/support/mobileConfig.js +99 -0
  53. package/shared/support/mobileConfig.test.js +113 -0
  54. package/shared/support/normalize.js +0 -108
  55. package/shared/support/normalize.test.js +0 -134
  56. package/shared/{actions/actionContributorHelpers.test.js → support/permissions.test.js} +1 -1
  57. package/shared/support/returnToPath.js +1 -20
  58. package/shared/support/returnToPath.test.js +1 -36
  59. package/shared/support/shellLayoutTargets.js +2 -140
  60. package/shared/support/shellLayoutTargets.test.js +1 -110
  61. package/shared/support/sorting.js +1 -9
  62. package/shared/surface/apiPaths.js +1 -82
  63. package/shared/surface/paths.js +38 -171
  64. package/shared/surface/registry.js +2 -1
  65. package/shared/surface/runtime.js +18 -109
  66. package/shared/surface/runtime.test.js +57 -0
  67. package/shared/validators/index.js +1 -4
  68. package/shared/validators/jsonRestSchemaSupport.js +0 -9
  69. package/shared/validators/recordIdParamsValidator.js +0 -6
  70. package/shared/validators/schemaDefinitions.js +4 -30
  71. package/shared/validators/schemaPayloadValidation.js +2 -6
  72. package/test/barrelExposure.test.js +3 -7
  73. package/test/exportsContract.test.js +1 -3
  74. package/client/mobileLaunchRouting.js +0 -231
  75. package/client/mobileLaunchRouting.test.js +0 -230
  76. package/server/http/lib/controller.js +0 -183
  77. package/server/http/lib/controller.test.js +0 -143
  78. package/server/http/lib/index.js +0 -5
  79. package/server/runtime/canonicalJson.js +0 -74
  80. package/server/runtime/entityChangeEvents.js +0 -307
  81. package/server/runtime/entityChangeEvents.test.js +0 -274
  82. package/server/runtime/fastifyBootstrap.js +0 -553
  83. package/server/runtime/moduleConfig.js +0 -223
  84. package/server/runtime/moduleConfig.test.js +0 -165
  85. package/server/runtime/pagination.js +0 -13
  86. package/server/runtime/realtimeNormalization.js +0 -21
  87. package/server/runtime/routeUtils.js +0 -20
  88. package/server/runtime/securityAudit.js +0 -269
  89. package/server/runtime/securityAudit.test.js +0 -41
  90. package/server/support/pageTargets.js +0 -772
  91. package/server/support/pageTargets.test.js +0 -659
  92. package/server/support/shellOutlets.js +0 -576
  93. package/server/support/shellOutlets.test.js +0 -336
  94. package/server/surface/index.js +0 -18
  95. package/shared/actions/audit.js +0 -7
  96. package/shared/actions/idempotency.js +0 -62
  97. package/shared/actions/observability.js +0 -10
  98. package/shared/actions/pipeline.js +0 -309
  99. package/shared/actions/requestMeta.js +0 -36
  100. package/shared/actions/textNormalization.js +0 -3
  101. package/shared/actions/withActionDefaults.js +0 -34
  102. /package/{shared → server}/actions/executionContext.test.js +0 -0
@@ -1,11 +1,6 @@
1
1
  import assert from "node:assert/strict";
2
2
  import test from "node:test";
3
- import {
4
- describeShellOutletTargets,
5
- discoverShellOutletTargetsFromVueSource,
6
- findShellOutletTargetById,
7
- normalizeShellOutletTargetId
8
- } from "./shellLayoutTargets.js";
3
+ import { normalizeShellOutletTargetId } from "./shellLayoutTargets.js";
9
4
 
10
5
  test("normalizeShellOutletTargetId validates host:position tokens", () => {
11
6
  assert.equal(normalizeShellOutletTargetId("shell-layout:primary-menu"), "shell-layout:primary-menu");
@@ -14,107 +9,3 @@ test("normalizeShellOutletTargetId validates host:position tokens", () => {
14
9
  assert.equal(normalizeShellOutletTargetId("shell-layout"), "");
15
10
  assert.equal(normalizeShellOutletTargetId("shell-layout:"), "");
16
11
  });
17
-
18
- test("discoverShellOutletTargetsFromVueSource resolves legal targets and one default", () => {
19
- const source = `
20
- <template>
21
- <ShellOutlet target="shell-layout:top-left" />
22
- <ShellOutlet
23
- target="shell-layout:primary-menu"
24
- default
25
- />
26
- <ShellOutlet target="shell-layout:secondary-menu" />
27
- </template>
28
- `;
29
-
30
- const discovered = discoverShellOutletTargetsFromVueSource(source, {
31
- context: "ShellLayout.vue"
32
- });
33
-
34
- assert.equal(discovered.defaultTargetId, "shell-layout:primary-menu");
35
- assert.deepEqual(
36
- discovered.targets.map((entry) => entry.id),
37
- ["shell-layout:top-left", "shell-layout:primary-menu", "shell-layout:secondary-menu"]
38
- );
39
- assert.equal(
40
- describeShellOutletTargets(discovered.targets),
41
- "shell-layout:top-left, shell-layout:primary-menu, shell-layout:secondary-menu"
42
- );
43
- assert.deepEqual(
44
- findShellOutletTargetById(discovered.targets, " shell-layout : primary-menu "),
45
- discovered.targets[1]
46
- );
47
- });
48
-
49
- test("discoverShellOutletTargetsFromVueSource throws for duplicate targets", () => {
50
- const source = `
51
- <template>
52
- <ShellOutlet target="shell-layout:top-right" />
53
- <ShellOutlet target="shell-layout:top-right" />
54
- </template>
55
- `;
56
-
57
- assert.throws(
58
- () => discoverShellOutletTargetsFromVueSource(source, { context: "ShellLayout.vue" }),
59
- /duplicate ShellOutlet target/
60
- );
61
- });
62
-
63
- test("discoverShellOutletTargetsFromVueSource throws for multiple defaults", () => {
64
- const source = `
65
- <template>
66
- <ShellOutlet target="shell-layout:primary-menu" default />
67
- <ShellOutlet target="shell-layout:secondary-menu" default />
68
- </template>
69
- `;
70
-
71
- assert.throws(
72
- () => discoverShellOutletTargetsFromVueSource(source, { context: "ShellLayout.vue" }),
73
- /multiple default ShellOutlet targets/
74
- );
75
- });
76
-
77
- test("discoverShellOutletTargetsFromVueSource can collect source hints without enforcing one default", () => {
78
- const source = `
79
- <template>
80
- <ShellOutlet target="shell-layout:primary-menu" default />
81
- <ShellOutlet target="shell-layout:primary-bottom-nav" default />
82
- </template>
83
- `;
84
-
85
- const discovered = discoverShellOutletTargetsFromVueSource(source, {
86
- context: "ShellLayout.vue",
87
- enforceSingleDefault: false
88
- });
89
-
90
- assert.equal(discovered.defaultTargetId, "shell-layout:primary-menu");
91
- assert.deepEqual(
92
- discovered.targets.map((entry) => entry.id),
93
- ["shell-layout:primary-menu", "shell-layout:primary-bottom-nav"]
94
- );
95
- });
96
-
97
- test("discoverShellOutletTargetsFromVueSource ignores disabled default markers", () => {
98
- const source = `
99
- <template>
100
- <ShellOutlet target="shell-layout:primary-menu" default="false" />
101
- <ShellOutlet target="shell-layout:secondary-menu" />
102
- </template>
103
- `;
104
-
105
- const discovered = discoverShellOutletTargetsFromVueSource(source, { context: "ShellLayout.vue" });
106
- assert.equal(discovered.defaultTargetId, "");
107
- });
108
-
109
- test("discoverShellOutletTargetsFromVueSource reads the target attribute", () => {
110
- const source = `
111
- <template>
112
- <ShellOutlet target="shell-layout:primary-menu" host="other-host" position="primary-menu" />
113
- </template>
114
- `;
115
-
116
- assert.deepEqual(
117
- discoverShellOutletTargetsFromVueSource(source, { context: "ShellLayout.vue" }).targets,
118
- [{ id: "shell-layout:primary-menu", default: false }]
119
- );
120
- });
@@ -4,12 +4,4 @@ function sortStrings(values = []) {
4
4
  );
5
5
  }
6
6
 
7
- function sortById(items = [], { idSelector = (item) => item?.id } = {}) {
8
- return [...(Array.isArray(items) ? items : [])].sort((left, right) => {
9
- const leftId = String(idSelector(left) || "").trim();
10
- const rightId = String(idSelector(right) || "").trim();
11
- return leftId.localeCompare(rightId);
12
- });
13
- }
14
-
15
- export { sortStrings, sortById };
7
+ export { sortStrings };
@@ -1,11 +1,4 @@
1
- import { matchesPathPrefix, normalizePathname } from "./paths.js";
2
-
3
- const API_BASE_PATH = "/api";
4
- const API_PREFIX = "/api";
5
- const API_PREFIX_SLASH = `${API_PREFIX}/`;
6
- const API_DOCS_PATH = `${API_PREFIX}/docs`;
7
- const API_REALTIME_PATH = `${API_PREFIX}/realtime`;
8
- const VERSIONED_API_PATH_PATTERN = /^\/api\/v[0-9]+(?:$|\/)/;
1
+ import { API_BASE_PATH, normalizePathname } from "./paths.js";
9
2
 
10
3
  function normalizeRouteTemplateParams(params = null) {
11
4
  if (!params || typeof params !== "object" || Array.isArray(params)) {
@@ -77,81 +70,7 @@ function resolveScopedApiBasePath({
77
70
  return `${basePath}${normalizedRelativePath}`;
78
71
  }
79
72
 
80
- function isApiPath(pathname) {
81
- return matchesPathPrefix(pathname, API_BASE_PATH);
82
- }
83
-
84
- function isVersionedApiPath(pathname) {
85
- return VERSIONED_API_PATH_PATTERN.test(normalizePathname(pathname));
86
- }
87
-
88
- function toVersionedApiPath(pathname) {
89
- const normalizedPathname = normalizePathname(pathname);
90
- if (!isApiPath(normalizedPathname)) {
91
- return normalizedPathname;
92
- }
93
-
94
- if (isVersionedApiPath(normalizedPathname)) {
95
- return normalizedPathname;
96
- }
97
-
98
- if (normalizedPathname === API_BASE_PATH) {
99
- return API_PREFIX;
100
- }
101
-
102
- return `${API_PREFIX}${normalizedPathname.slice(API_BASE_PATH.length)}`;
103
- }
104
-
105
- function toVersionedApiPrefix(pathnameOrPrefix) {
106
- const versionedPath = toVersionedApiPath(pathnameOrPrefix || API_BASE_PATH);
107
- if (!isApiPath(versionedPath)) {
108
- return normalizePathname(versionedPath);
109
- }
110
-
111
- if (versionedPath === API_PREFIX) {
112
- return API_PREFIX_SLASH;
113
- }
114
-
115
- return `${versionedPath}/`;
116
- }
117
-
118
- function buildVersionedApiPath(suffix) {
119
- const rawSuffix = String(suffix || "").trim();
120
- if (!rawSuffix || rawSuffix === "/") {
121
- return API_PREFIX;
122
- }
123
-
124
- const normalizedSuffix = normalizePathname(rawSuffix.startsWith("/") ? rawSuffix : `/${rawSuffix}`);
125
- if (isApiPath(normalizedSuffix)) {
126
- return toVersionedApiPath(normalizedSuffix);
127
- }
128
-
129
- if (normalizedSuffix === "/") {
130
- return API_PREFIX;
131
- }
132
-
133
- return `${API_PREFIX}${normalizedSuffix}`;
134
- }
135
-
136
- function isVersionedApiPrefixMatch(pathname) {
137
- const normalizedPathname = normalizePathname(pathname);
138
- return normalizedPathname === API_PREFIX || normalizedPathname.startsWith(API_PREFIX_SLASH);
139
- }
140
-
141
73
  export {
142
- API_BASE_PATH,
143
- API_PREFIX,
144
- API_PREFIX_SLASH,
145
- API_DOCS_PATH,
146
- API_REALTIME_PATH,
147
- materializeRouteTemplate,
148
- normalizePathname,
149
- isApiPath,
150
- isVersionedApiPath,
151
- toVersionedApiPath,
152
- toVersionedApiPrefix,
153
- buildVersionedApiPath,
154
- isVersionedApiPrefixMatch,
155
74
  resolveScopedRouteBase,
156
75
  resolveScopedApiBasePath
157
76
  };
@@ -1,4 +1,7 @@
1
1
  import { escapeRegExp } from "./escapeRegExp.js";
2
+ import { normalizeSurfaceId } from "./registry.js";
3
+
4
+ const API_BASE_PATH = "/api";
2
5
 
3
6
  function normalizePathname(pathname) {
4
7
  const rawValue = String(pathname || "/").trim();
@@ -73,201 +76,65 @@ function compareSurfaceRouteSpecificity(left, right) {
73
76
  return String(left.id || "").localeCompare(String(right.id || ""));
74
77
  }
75
78
 
76
- function createSurfacePathHelpers(options = {}) {
77
- const normalizeSurfaceId = options?.normalizeSurfaceId;
78
- const resolveSurfaceRouteBaseFromRegistry = options?.resolveSurfaceRouteBase;
79
- const listSurfaceDefinitions = options?.listSurfaceDefinitions;
80
-
81
- if (typeof normalizeSurfaceId !== "function") {
82
- throw new Error("createSurfacePathHelpers requires normalizeSurfaceId.");
83
- }
84
- if (typeof resolveSurfaceRouteBaseFromRegistry !== "function") {
85
- throw new Error("createSurfacePathHelpers requires resolveSurfaceRouteBase.");
86
- }
87
- if (typeof listSurfaceDefinitions !== "function") {
88
- throw new Error("createSurfacePathHelpers requires listSurfaceDefinitions.");
89
- }
90
-
91
- const defaultSurfaceId = normalizeSurfaceId(options?.defaultSurfaceId);
92
- if (!defaultSurfaceId) {
79
+ function createSurfacePathHelpers({ defaultSurfaceId, surfaceDefinitions, apiBasePath = API_BASE_PATH } = {}) {
80
+ const normalizedDefaultSurfaceId = normalizeSurfaceId(defaultSurfaceId);
81
+ if (!normalizedDefaultSurfaceId) {
93
82
  throw new Error("createSurfacePathHelpers requires defaultSurfaceId.");
94
83
  }
95
-
96
- const apiBasePath = normalizePathname(options?.apiBasePath || "/api");
97
- const routeConfig = options?.routes && typeof options.routes === "object" && !Array.isArray(options.routes) ? options.routes : {};
98
-
99
- function normalizeSurface(surface) {
100
- return normalizeSurfaceId(surface);
101
- }
102
-
103
- function resolveSurfaceRouteBase(surface) {
104
- return normalizePathname(resolveSurfaceRouteBaseFromRegistry(surface));
84
+ if (!Array.isArray(surfaceDefinitions)) {
85
+ throw new Error("createSurfacePathHelpers requires a surfaceDefinitions array.");
105
86
  }
106
87
 
107
- function surfaceDefinitions() {
108
- return listSurfaceDefinitions().filter(
109
- (surface) => surface && typeof surface === "object" && !Array.isArray(surface)
110
- );
111
- }
88
+ const normalizedApiBasePath = normalizePathname(apiBasePath);
89
+ const routedSurfaces = surfaceDefinitions
90
+ .map((definition) => {
91
+ const surfaceId = normalizeSurfaceId(definition?.id);
92
+ if (!surfaceId) {
93
+ return null;
94
+ }
112
95
 
113
- function routedSurfaceDefinitions() {
114
- return surfaceDefinitions()
115
- .map((definition) => {
116
- const surfaceId = normalizeSurface(definition?.id);
117
- if (!surfaceId) {
118
- return null;
119
- }
96
+ const matcher = compileSurfaceRouteMatcher(definition.routeBase || "/");
97
+ return {
98
+ id: surfaceId,
99
+ routeBase: matcher.routeBase,
100
+ segmentCount: matcher.segmentCount,
101
+ staticSegmentCount: matcher.staticSegmentCount,
102
+ matcher
103
+ };
104
+ })
105
+ .filter(Boolean)
106
+ .sort(compareSurfaceRouteSpecificity);
120
107
 
121
- const routeBase = resolveSurfaceRouteBase(surfaceId);
122
- const matcher = compileSurfaceRouteMatcher(routeBase);
123
- return {
124
- id: surfaceId,
125
- routeBase,
126
- segmentCount: matcher.segmentCount,
127
- staticSegmentCount: matcher.staticSegmentCount,
128
- matcher
129
- };
130
- })
131
- .filter(Boolean)
132
- .sort(compareSurfaceRouteSpecificity);
108
+ function matchSurface(pathname) {
109
+ return routedSurfaces.find((surface) => surface.matcher.test(pathname))?.id || normalizedDefaultSurfaceId;
133
110
  }
134
111
 
135
112
  function resolveApiNamespace(pathname) {
136
- const normalizedPathname = normalizePathname(pathname);
137
- if (!matchesPathPrefix(normalizedPathname, apiBasePath)) {
113
+ if (!matchesPathPrefix(pathname, normalizedApiBasePath)) {
138
114
  return "";
139
115
  }
140
116
 
141
- if (normalizedPathname === apiBasePath) {
142
- return apiBasePath;
143
- }
144
-
145
- const remainder = normalizedPathname.slice(apiBasePath.length);
146
- if (!remainder || remainder === "/") {
147
- return apiBasePath;
148
- }
149
-
150
- const versionMatch = remainder.match(/^\/v[0-9]+(?:$|\/)/);
117
+ const versionMatch = pathname.slice(normalizedApiBasePath.length).match(/^\/v[0-9]+(?:$|\/)/);
151
118
  if (!versionMatch) {
152
- return apiBasePath;
153
- }
154
-
155
- const versionPath = String(versionMatch[0]).replace(/\/$/, "");
156
- return normalizePathname(`${apiBasePath}${versionPath}`);
157
- }
158
-
159
- function resolveSurfaceFromApiPathname(pathname) {
160
- const apiNamespace = resolveApiNamespace(pathname);
161
- if (!apiNamespace) {
162
- return "";
163
- }
164
-
165
- const normalizedPathname = normalizePathname(pathname);
166
- const remainder = normalizedPathname.slice(apiNamespace.length) || "/";
167
- const apiRelativePath = normalizePathname(remainder.startsWith("/") ? remainder : `/${remainder}`);
168
-
169
- for (const surface of routedSurfaceDefinitions()) {
170
- if (surface.matcher.test(apiRelativePath)) {
171
- return surface.id;
172
- }
119
+ return normalizedApiBasePath;
173
120
  }
174
121
 
175
- return defaultSurfaceId;
122
+ return normalizePathname(`${normalizedApiBasePath}${versionMatch[0]}`);
176
123
  }
177
124
 
178
125
  function resolveSurfaceFromPathname(pathname) {
179
126
  const normalizedPathname = normalizePathname(pathname);
180
- const apiSurface = resolveSurfaceFromApiPathname(normalizedPathname);
181
- if (apiSurface) {
182
- return apiSurface;
183
- }
184
-
185
- for (const surface of routedSurfaceDefinitions()) {
186
- if (surface.matcher.test(normalizedPathname)) {
187
- return surface.id;
188
- }
189
- }
190
-
191
- return defaultSurfaceId;
192
- }
193
-
194
- function withSurfaceRouteBase(surface, path) {
195
- const normalizedPath = normalizePathname(path);
196
- const routeBase = resolveSurfaceRouteBase(surface);
197
- if (normalizedPath === "/") {
198
- return routeBase || "/";
199
- }
200
-
201
- if (routeBase === "/") {
202
- return normalizedPath;
203
- }
204
- return normalizePathname(`${routeBase}${normalizedPath}`);
205
- }
206
-
207
- function normalizeOptionalRoutePath(path) {
208
- const normalizedPath = String(path || "").trim();
209
- if (!normalizedPath) {
210
- return "";
211
- }
212
-
213
- return normalizePathname(normalizedPath);
214
- }
215
-
216
- function createSurfacePaths(surface) {
217
- const normalizedSurface = normalizeSurface(surface);
218
- const routeBase = resolveSurfaceRouteBase(normalizedSurface);
219
-
220
- const rootPath = routeBase || "/";
221
- const namedPaths = {};
222
- for (const [routeName, routePath] of Object.entries(routeConfig)) {
223
- const normalizedRouteName = String(routeName || "").trim();
224
- if (!normalizedRouteName) {
225
- continue;
226
- }
227
- const normalizedRoutePath = normalizeOptionalRoutePath(routePath);
228
- if (!normalizedRoutePath) {
229
- continue;
230
- }
231
- namedPaths[normalizedRouteName] = withSurfaceRouteBase(normalizedSurface, normalizedRoutePath);
127
+ const apiNamespace = resolveApiNamespace(normalizedPathname);
128
+ if (!apiNamespace) {
129
+ return matchSurface(normalizedPathname);
232
130
  }
233
131
 
234
- const frozenNamedPaths = Object.freeze({
235
- ...namedPaths
236
- });
237
-
238
- return {
239
- surface: normalizedSurface,
240
- routeBase,
241
- rootPath,
242
- namedPaths: frozenNamedPaths,
243
- isNamedPath(routeName, pathname) {
244
- const normalizedRouteName = String(routeName || "").trim();
245
- if (!normalizedRouteName || !Object.hasOwn(frozenNamedPaths, normalizedRouteName)) {
246
- return false;
247
- }
248
- return normalizePathname(pathname) === frozenNamedPaths[normalizedRouteName];
249
- }
250
- };
251
- }
252
-
253
- function resolveSurfacePaths(pathname) {
254
- const browserPathname =
255
- typeof window !== "undefined" && window?.location?.pathname ? String(window.location.pathname) : "";
256
- const effectivePathname = browserPathname || pathname;
257
- return createSurfacePaths(resolveSurfaceFromPathname(effectivePathname));
132
+ return matchSurface(normalizePathname(normalizedPathname.slice(apiNamespace.length)));
258
133
  }
259
134
 
260
135
  return Object.freeze({
261
- normalizePathname,
262
- matchesPathPrefix,
263
- compileSurfaceRouteMatcher,
264
- resolveSurfaceFromApiPathname,
265
- resolveSurfaceFromPathname,
266
- resolveSurfaceRouteBase,
267
- withSurfaceRouteBase,
268
- createSurfacePaths,
269
- resolveSurfacePaths
136
+ resolveSurfaceFromPathname
270
137
  });
271
138
  }
272
139
 
273
- export { createSurfacePathHelpers, normalizePathname, matchesPathPrefix };
140
+ export { API_BASE_PATH, createSurfacePathHelpers, normalizePathname, matchesPathPrefix };
@@ -81,7 +81,8 @@ function createSurfaceRegistry(options = {}) {
81
81
  ...sourceDefinition,
82
82
  id: normalizedId,
83
83
  pagesRoot,
84
- routeBase: deriveSurfaceRouteBaseFromPagesRoot(pagesRoot)
84
+ routeBase: deriveSurfaceRouteBaseFromPagesRoot(pagesRoot),
85
+ enabled: sourceDefinition.enabled !== false
85
86
  })
86
87
  ];
87
88
  })
@@ -1,41 +1,9 @@
1
1
  import { createSurfacePathHelpers } from "./paths.js";
2
- import {
3
- createSurfaceRegistry,
4
- deriveSurfaceRouteBaseFromPagesRoot,
5
- normalizeSurfaceId,
6
- normalizeSurfacePagesRoot
7
- } from "./registry.js";
2
+ import { createSurfaceRegistry, normalizeSurfaceId } from "./registry.js";
8
3
  import { isRecord } from "../support/normalize.js";
9
4
 
10
- function uniqueSurfaceIds(ids) {
11
- const seen = new Set();
12
- const ordered = [];
13
- for (const id of ids) {
14
- const normalizedId = normalizeSurfaceId(id);
15
- if (!normalizedId || seen.has(normalizedId)) {
16
- continue;
17
- }
18
- seen.add(normalizedId);
19
- ordered.push(normalizedId);
20
- }
21
- return ordered;
22
- }
23
-
24
- function resolveSurfaceIds({ surfaces = {} } = {}) {
25
- const surfaceCandidates = [];
26
- for (const [key, value] of Object.entries(isRecord(surfaces) ? surfaces : {})) {
27
- const record = isRecord(value) ? value : {};
28
- surfaceCandidates.push(record.id || key);
29
- }
30
-
31
- const fromSurfaces = uniqueSurfaceIds(surfaceCandidates);
32
- if (fromSurfaces.length > 0) {
33
- return fromSurfaces;
34
- }
35
-
36
- throw new Error("createSurfaceRuntime requires at least one surface id.");
37
- }
38
-
5
+ // Unlike normalizePathname, this keeps "?" so vue-router optional params
6
+ // such as "/users/:id?" survive when a route is hoisted to an absolute path.
39
7
  function normalizeRoutePath(pathLike = "") {
40
8
  const rawPath = String(pathLike || "").trim();
41
9
  if (!rawPath || rawPath === "/") {
@@ -44,9 +12,6 @@ function normalizeRoutePath(pathLike = "") {
44
12
 
45
13
  const withLeadingSlash = rawPath.startsWith("/") ? rawPath : `/${rawPath}`;
46
14
  const compacted = withLeadingSlash.replace(/\/{2,}/g, "/");
47
- if (compacted === "/") {
48
- return "/";
49
- }
50
15
  return compacted.replace(/\/+$/, "") || "/";
51
16
  }
52
17
 
@@ -99,16 +64,7 @@ function resolveRouteSurface(route, inheritedSurfaceId = "", { surfaceRuntime, a
99
64
  return normalizedInheritedSurface;
100
65
  }
101
66
 
102
- const routePath = String(route?.path || "").trim();
103
- if (routePath && routePath.startsWith("/")) {
104
- return surfaceRuntime.resolveSurfaceFromPathname(routePath);
105
- }
106
-
107
- if (absolutePath) {
108
- return surfaceRuntime.resolveSurfaceFromPathname(absolutePath);
109
- }
110
-
111
- return surfaceRuntime.DEFAULT_SURFACE_ID;
67
+ return surfaceRuntime.resolveSurfaceFromPathname(absolutePath);
112
68
  }
113
69
 
114
70
  function normalizeRoutesBySurfaceBoundaries(routeList = [], { surfaceRuntime } = {}) {
@@ -186,48 +142,19 @@ function normalizeRoutesBySurfaceBoundaries(routeList = [], { surfaceRuntime } =
186
142
 
187
143
  function createSurfaceRuntime(options = {}) {
188
144
  const allMode = normalizeSurfaceId(options?.allMode || "all") || "all";
189
- const sourceSurfaces = isRecord(options?.surfaces) ? options.surfaces : {};
190
- const surfaceIds = resolveSurfaceIds({
191
- surfaces: sourceSurfaces
192
- });
193
-
194
- const normalizedSurfaces = {};
195
- for (const surfaceId of surfaceIds) {
196
- const source = isRecord(sourceSurfaces[surfaceId]) ? sourceSurfaces[surfaceId] : {};
197
- if (!Object.prototype.hasOwnProperty.call(source, "pagesRoot")) {
198
- throw new Error(`Surface "${surfaceId}" requires pagesRoot (use "" for root).`);
199
- }
200
- const pagesRoot = normalizeSurfacePagesRoot(source.pagesRoot);
201
- normalizedSurfaces[surfaceId] = {
202
- ...source,
203
- id: surfaceId,
204
- pagesRoot,
205
- routeBase: deriveSurfaceRouteBaseFromPagesRoot(pagesRoot),
206
- enabled: source.enabled !== false
207
- };
208
- }
209
-
210
- const defaultSurfaceId = normalizeSurfaceId(options?.defaultSurfaceId) || surfaceIds[0];
211
145
  const registry = createSurfaceRegistry({
212
- surfaces: normalizedSurfaces,
213
- defaultSurfaceId
146
+ surfaces: isRecord(options?.surfaces) ? options.surfaces : {},
147
+ defaultSurfaceId: options?.defaultSurfaceId
214
148
  });
149
+ const { SURFACE_REGISTRY, DEFAULT_SURFACE_ID } = registry;
150
+ const surfaceDefinitions = registry.listSurfaceDefinitions();
151
+ const surfaceIds = surfaceDefinitions.map((definition) => definition.id);
152
+ const enabledSurfaceIds = surfaceDefinitions.filter((definition) => definition.enabled).map((definition) => definition.id);
215
153
 
216
154
  const pathHelpers = createSurfacePathHelpers({
217
- apiBasePath: String(options?.apiBasePath || "/api"),
218
- defaultSurfaceId: registry.DEFAULT_SURFACE_ID,
219
- normalizeSurfaceId: registry.normalizeSurfaceId,
220
- resolveSurfaceRouteBase: registry.resolveSurfaceRouteBase,
221
- listSurfaceDefinitions: registry.listSurfaceDefinitions,
222
- routes: options?.routes
223
- });
224
-
225
- const enabledSurfaceIds = surfaceIds.filter((surfaceId) => normalizedSurfaces[surfaceId]?.enabled !== false);
226
- const defaultSurfaceSource = isRecord(normalizedSurfaces[registry.DEFAULT_SURFACE_ID])
227
- ? normalizedSurfaces[registry.DEFAULT_SURFACE_ID]
228
- : {};
229
- const defaultSurfaceDefinition = Object.freeze({
230
- ...defaultSurfaceSource
155
+ apiBasePath: options?.apiBasePath,
156
+ defaultSurfaceId: DEFAULT_SURFACE_ID,
157
+ surfaceDefinitions
231
158
  });
232
159
 
233
160
  function normalizeSurfaceMode(value) {
@@ -236,7 +163,7 @@ function createSurfaceRuntime(options = {}) {
236
163
  return allMode;
237
164
  }
238
165
 
239
- return registry.SURFACE_REGISTRY[normalized] ? normalized : allMode;
166
+ return SURFACE_REGISTRY[normalized] ? normalized : allMode;
240
167
  }
241
168
 
242
169
  function listEnabledSurfaceIds() {
@@ -244,29 +171,11 @@ function createSurfaceRuntime(options = {}) {
244
171
  }
245
172
 
246
173
  function getSurfaceDefinition(surfaceId) {
247
- const normalizedSurfaceId = normalizeSurfaceId(surfaceId);
248
- const definition = normalizedSurfaces[normalizedSurfaceId];
249
- if (!definition) {
250
- return null;
251
- }
252
-
253
- return Object.freeze({
254
- ...definition
255
- });
174
+ return SURFACE_REGISTRY[normalizeSurfaceId(surfaceId)] || null;
256
175
  }
257
176
 
258
177
  function listSurfaceDefinitions({ enabledOnly = false } = {}) {
259
- const ids = enabledOnly ? enabledSurfaceIds : surfaceIds;
260
- const definitions = [];
261
- for (const surfaceId of ids) {
262
- const source = isRecord(normalizedSurfaces[surfaceId]) ? normalizedSurfaces[surfaceId] : {};
263
- definitions.push(
264
- Object.freeze({
265
- ...source
266
- })
267
- );
268
- }
269
- return definitions;
178
+ return enabledOnly ? surfaceDefinitions.filter((definition) => definition.enabled) : [...surfaceDefinitions];
270
179
  }
271
180
 
272
181
  function isSurfaceEnabled(surfaceId) {
@@ -281,8 +190,8 @@ function createSurfaceRuntime(options = {}) {
281
190
  return {
282
191
  SURFACE_MODE_ALL: allMode,
283
192
  SURFACE_IDS: [...surfaceIds],
284
- DEFAULT_SURFACE_ID: registry.DEFAULT_SURFACE_ID,
285
- DEFAULT_SURFACE: defaultSurfaceDefinition,
193
+ DEFAULT_SURFACE_ID,
194
+ DEFAULT_SURFACE: SURFACE_REGISTRY[DEFAULT_SURFACE_ID],
286
195
  normalizeSurfaceMode,
287
196
  resolveSurfaceFromPathname: pathHelpers.resolveSurfaceFromPathname,
288
197
  getSurfaceDefinition,