@jskit-ai/kernel 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/README.md +24 -0
- package/_testable/index.js +4 -0
- package/client/appConfig.js +33 -0
- package/client/componentInteraction.js +51 -0
- package/client/componentInteraction.test.js +111 -0
- package/client/descriptorSections.js +75 -0
- package/client/index.d.ts +70 -0
- package/client/index.js +3 -0
- package/client/logging.js +38 -0
- package/client/moduleBootstrap.js +670 -0
- package/client/moduleBootstrap.test.js +403 -0
- package/client/shellBootstrap.js +233 -0
- package/client/shellBootstrap.test.js +185 -0
- package/client/shellRouting.js +321 -0
- package/client/shellRouting.test.js +113 -0
- package/client/vite/clientBootstrapPlugin.js +259 -0
- package/client/vite/clientBootstrapPlugin.test.js +563 -0
- package/client/vite/index.js +3 -0
- package/internal/node/fileSystem.js +21 -0
- package/internal/node/installedPackageDescriptor.js +104 -0
- package/package.json +43 -0
- package/server/actions/ActionRuntimeServiceProvider.js +309 -0
- package/server/actions/ActionRuntimeServiceProvider.test.js +551 -0
- package/server/actions/index.js +8 -0
- package/server/container/ContainerCoreServiceProvider.js +27 -0
- package/server/container/index.js +10 -0
- package/server/exportPolicy.test.js +68 -0
- package/server/http/HttpFastifyServiceProvider.js +25 -0
- package/server/http/_testable/index.js +2 -0
- package/server/http/index.js +1 -0
- package/server/http/lib/controller.js +183 -0
- package/server/http/lib/controller.test.js +143 -0
- package/server/http/lib/errors.js +12 -0
- package/server/http/lib/httpRuntime.js +82 -0
- package/server/http/lib/index.js +18 -0
- package/server/http/lib/kernel.js +15 -0
- package/server/http/lib/kernel.test.js +880 -0
- package/server/http/lib/middlewareRuntime.js +149 -0
- package/server/http/lib/requestActionExecutor.js +258 -0
- package/server/http/lib/requestScope.js +59 -0
- package/server/http/lib/routeRegistration.js +165 -0
- package/server/http/lib/routeSupport.js +45 -0
- package/server/http/lib/routeValidator.js +469 -0
- package/server/http/lib/routeValidator.test.js +474 -0
- package/server/http/lib/router.js +206 -0
- package/server/kernel/KernelCoreServiceProvider.js +27 -0
- package/server/kernel/index.js +10 -0
- package/server/platform/PlatformServerRuntimeServiceProvider.js +30 -0
- package/server/platform/index.js +5 -0
- package/server/platform/providerRuntime/descriptorCatalog.js +170 -0
- package/server/platform/providerRuntime/helpers.js +45 -0
- package/server/platform/providerRuntime/lockfile.js +27 -0
- package/server/platform/providerRuntime/providerLoader.js +283 -0
- package/server/platform/providerRuntime.js +142 -0
- package/server/platform/providerRuntime.test.js +217 -0
- package/server/platform/runtime.js +40 -0
- package/server/platform/surfaceRuntime.js +150 -0
- package/server/platform/surfaceRuntime.test.js +136 -0
- package/server/registries/actionSurfaceSourceRegistry.js +150 -0
- package/server/registries/bootstrapPayloadContributorRegistry.js +41 -0
- package/server/registries/domainEventListenerRegistry.js +61 -0
- package/server/registries/index.js +36 -0
- package/server/registries/primitives.js +63 -0
- package/server/registries/routeVisibilityResolverRegistry.js +87 -0
- package/server/registries/serviceRegistrationRegistry.js +431 -0
- package/server/runtime/ServerRuntimeCoreServiceProvider.js +65 -0
- package/server/runtime/ServerRuntimeCoreServiceProvider.test.js +53 -0
- package/server/runtime/apiRoutePolicyParity.test.js +109 -0
- package/server/runtime/apiRouteRegistration.js +65 -0
- package/server/runtime/bootBootstrapRoutes.js +46 -0
- package/server/runtime/bootBootstrapRoutes.test.js +79 -0
- package/server/runtime/bootstrapContributors.test.js +114 -0
- package/server/runtime/canonicalJson.js +74 -0
- package/server/runtime/composition.js +142 -0
- package/server/runtime/domainEvents.test.js +114 -0
- package/server/runtime/domainRules.js +50 -0
- package/server/runtime/domainRules.test.js +87 -0
- package/server/runtime/entityChangeEvents.js +182 -0
- package/server/runtime/entityChangeEvents.test.js +211 -0
- package/server/runtime/errors.js +68 -0
- package/server/runtime/errors.test.js +73 -0
- package/server/runtime/fastifyBootstrap.js +372 -0
- package/server/runtime/fastifyBootstrap.test.js +194 -0
- package/server/runtime/index.js +6 -0
- package/server/runtime/integers.js +13 -0
- package/server/runtime/moduleConfig.js +269 -0
- package/server/runtime/moduleConfig.test.js +141 -0
- package/server/runtime/pagination.js +13 -0
- package/server/runtime/realtimeNormalization.js +21 -0
- package/server/runtime/requestUrl.js +38 -0
- package/server/runtime/routeUtils.js +20 -0
- package/server/runtime/runtimeAssembly.js +113 -0
- package/server/runtime/runtimeKernel.js +55 -0
- package/server/runtime/securityAudit.js +269 -0
- package/server/runtime/securityAudit.test.js +41 -0
- package/server/runtime/serviceAuthorization.js +113 -0
- package/server/runtime/serviceAuthorization.test.js +100 -0
- package/server/runtime/serviceRegistration.test.js +197 -0
- package/server/support/SupportCoreServiceProvider.js +25 -0
- package/server/support/appConfig.js +37 -0
- package/server/support/appConfig.test.js +94 -0
- package/server/support/defaultMissingHandler.js +7 -0
- package/server/support/index.js +2 -0
- package/server/support/routePolicyConfig.js +51 -0
- package/server/support/symlinkSafeRequire.js +78 -0
- package/server/support/symlinkSafeRequire.test.js +27 -0
- package/server/surface/SurfaceRoutingServiceProvider.js +27 -0
- package/server/surface/index.js +19 -0
- package/shared/actions/actionContributorHelpers.js +34 -0
- package/shared/actions/actionContributorHelpers.test.js +16 -0
- package/shared/actions/actionDefinitions.js +488 -0
- package/shared/actions/actionDefinitions.test.js +212 -0
- package/shared/actions/audit.js +7 -0
- package/shared/actions/executionContext.js +97 -0
- package/shared/actions/executionContext.test.js +66 -0
- package/shared/actions/idempotency.js +62 -0
- package/shared/actions/index.js +2 -0
- package/shared/actions/observability.js +10 -0
- package/shared/actions/pipeline.js +287 -0
- package/shared/actions/policies.js +342 -0
- package/shared/actions/policies.test.js +233 -0
- package/shared/actions/registry.js +187 -0
- package/shared/actions/registry.test.js +381 -0
- package/shared/actions/requestMeta.js +36 -0
- package/shared/actions/textNormalization.js +3 -0
- package/shared/actions/withActionDefaults.js +34 -0
- package/shared/index.js +2 -0
- package/shared/runtime/application.js +323 -0
- package/shared/runtime/container.js +261 -0
- package/shared/runtime/containerErrors.js +22 -0
- package/shared/runtime/index.js +18 -0
- package/shared/runtime/kernelErrors.js +20 -0
- package/shared/runtime/serviceProvider.js +13 -0
- package/shared/support/formatDateTime.js +10 -0
- package/shared/support/formatDateTime.test.js +15 -0
- package/shared/support/index.js +14 -0
- package/shared/support/linkPath.js +67 -0
- package/shared/support/linkPath.test.js +35 -0
- package/shared/support/normalize.js +116 -0
- package/shared/support/normalize.test.js +48 -0
- package/shared/support/packageDescriptor.test.js +121 -0
- package/shared/support/permissions.js +50 -0
- package/shared/support/pickOwnProperties.js +17 -0
- package/shared/support/pickOwnProperties.test.js +25 -0
- package/shared/support/policies.js +11 -0
- package/shared/support/queryPath.js +33 -0
- package/shared/support/queryPath.test.js +19 -0
- package/shared/support/queryResilience.js +34 -0
- package/shared/support/queryResilience.test.js +33 -0
- package/shared/support/returnToPath.js +153 -0
- package/shared/support/returnToPath.test.js +123 -0
- package/shared/support/sorting.js +15 -0
- package/shared/support/tokens.js +23 -0
- package/shared/support/tokens.test.js +17 -0
- package/shared/support/visibility.js +56 -0
- package/shared/support/visibility.test.js +45 -0
- package/shared/surface/apiPaths.js +84 -0
- package/shared/surface/escapeRegExp.js +5 -0
- package/shared/surface/index.js +6 -0
- package/shared/surface/paths.js +273 -0
- package/shared/surface/registry.js +135 -0
- package/shared/surface/registry.test.js +44 -0
- package/shared/surface/runtime.js +357 -0
- package/shared/surface/runtime.test.js +319 -0
- package/shared/validators/createCursorListValidator.js +42 -0
- package/shared/validators/createCursorListValidator.test.js +34 -0
- package/shared/validators/cursorPaginationQueryValidator.js +31 -0
- package/shared/validators/cursorPaginationQueryValidator.test.js +21 -0
- package/shared/validators/index.js +12 -0
- package/shared/validators/inputNormalization.js +13 -0
- package/shared/validators/mergeObjectSchemas.js +31 -0
- package/shared/validators/mergeObjectSchemas.test.js +67 -0
- package/shared/validators/mergeValidators.js +89 -0
- package/shared/validators/mergeValidators.test.js +116 -0
- package/shared/validators/nestValidator.js +53 -0
- package/shared/validators/nestValidator.test.js +60 -0
- package/shared/validators/recordIdParamsValidator.js +36 -0
- package/shared/validators/recordIdParamsValidator.test.js +20 -0
- package/shared/validators/resourceRequiredMetadata.js +41 -0
- package/shared/validators/resourceRequiredMetadata.test.js +49 -0
- package/test/barrelExposure.test.js +106 -0
- package/test/dynamicImportPolicy.test.js +89 -0
- package/test/exportsContract.test.js +168 -0
- package/test/routeInputContractGuard.test.js +78 -0
- package/test/surfaceIndependence.test.js +109 -0
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
import { escapeRegExp } from "./escapeRegExp.js";
|
|
2
|
+
|
|
3
|
+
function normalizePathname(pathname) {
|
|
4
|
+
const rawValue = String(pathname || "/").trim();
|
|
5
|
+
if (!rawValue) {
|
|
6
|
+
return "/";
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const withoutQuery = rawValue.split("?")[0].split("#")[0];
|
|
10
|
+
const withLeadingSlash = withoutQuery.startsWith("/") ? withoutQuery : `/${withoutQuery}`;
|
|
11
|
+
const squashed = withLeadingSlash.replace(/\/{2,}/g, "/");
|
|
12
|
+
if (squashed === "/") {
|
|
13
|
+
return "/";
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return squashed.replace(/\/+$/, "") || "/";
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function matchesPathPrefix(pathname, prefix) {
|
|
20
|
+
const normalizedPathname = normalizePathname(pathname);
|
|
21
|
+
const normalizedPrefix = normalizePathname(prefix);
|
|
22
|
+
return normalizedPathname === normalizedPrefix || normalizedPathname.startsWith(`${normalizedPrefix}/`);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function compileSurfaceRouteMatcher(routeBase) {
|
|
26
|
+
const normalizedRouteBase = normalizePathname(routeBase);
|
|
27
|
+
const segments = normalizedRouteBase.split("/").filter(Boolean);
|
|
28
|
+
if (segments.length < 1) {
|
|
29
|
+
return Object.freeze({
|
|
30
|
+
routeBase: "/",
|
|
31
|
+
segmentCount: 0,
|
|
32
|
+
staticSegmentCount: 0,
|
|
33
|
+
test(pathname) {
|
|
34
|
+
return normalizePathname(pathname).startsWith("/");
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const patternSegments = segments.map((segment) =>
|
|
40
|
+
segment.startsWith(":") && segment.length > 1 ? "[^/]+" : escapeRegExp(segment)
|
|
41
|
+
);
|
|
42
|
+
const pattern = new RegExp(`^/${patternSegments.join("/")}(?:$|/)`);
|
|
43
|
+
const staticSegmentCount = segments.filter(
|
|
44
|
+
(segment) => !(segment.startsWith(":") && segment.length > 1)
|
|
45
|
+
).length;
|
|
46
|
+
|
|
47
|
+
return Object.freeze({
|
|
48
|
+
routeBase: normalizedRouteBase,
|
|
49
|
+
segmentCount: segments.length,
|
|
50
|
+
staticSegmentCount,
|
|
51
|
+
test(pathname) {
|
|
52
|
+
return pattern.test(normalizePathname(pathname));
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function compareSurfaceRouteSpecificity(left, right) {
|
|
58
|
+
const staticDiff = right.staticSegmentCount - left.staticSegmentCount;
|
|
59
|
+
if (staticDiff !== 0) {
|
|
60
|
+
return staticDiff;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const segmentDiff = right.segmentCount - left.segmentCount;
|
|
64
|
+
if (segmentDiff !== 0) {
|
|
65
|
+
return segmentDiff;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const lengthDiff = String(right.routeBase || "").length - String(left.routeBase || "").length;
|
|
69
|
+
if (lengthDiff !== 0) {
|
|
70
|
+
return lengthDiff;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return String(left.id || "").localeCompare(String(right.id || ""));
|
|
74
|
+
}
|
|
75
|
+
|
|
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) {
|
|
93
|
+
throw new Error("createSurfacePathHelpers requires defaultSurfaceId.");
|
|
94
|
+
}
|
|
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));
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function surfaceDefinitions() {
|
|
108
|
+
return listSurfaceDefinitions().filter(
|
|
109
|
+
(surface) => surface && typeof surface === "object" && !Array.isArray(surface)
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function routedSurfaceDefinitions() {
|
|
114
|
+
return surfaceDefinitions()
|
|
115
|
+
.map((definition) => {
|
|
116
|
+
const surfaceId = normalizeSurface(definition?.id);
|
|
117
|
+
if (!surfaceId) {
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
|
|
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);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function resolveApiNamespace(pathname) {
|
|
136
|
+
const normalizedPathname = normalizePathname(pathname);
|
|
137
|
+
if (!matchesPathPrefix(normalizedPathname, apiBasePath)) {
|
|
138
|
+
return "";
|
|
139
|
+
}
|
|
140
|
+
|
|
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]+(?:$|\/)/);
|
|
151
|
+
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
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return defaultSurfaceId;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function resolveSurfaceFromPathname(pathname) {
|
|
179
|
+
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);
|
|
232
|
+
}
|
|
233
|
+
|
|
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));
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
return Object.freeze({
|
|
261
|
+
normalizePathname,
|
|
262
|
+
matchesPathPrefix,
|
|
263
|
+
compileSurfaceRouteMatcher,
|
|
264
|
+
resolveSurfaceFromApiPathname,
|
|
265
|
+
resolveSurfaceFromPathname,
|
|
266
|
+
resolveSurfaceRouteBase,
|
|
267
|
+
withSurfaceRouteBase,
|
|
268
|
+
createSurfacePaths,
|
|
269
|
+
resolveSurfacePaths
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
export { createSurfacePathHelpers, normalizePathname, matchesPathPrefix };
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
function normalizeSurfaceId(value) {
|
|
2
|
+
return String(value || "")
|
|
3
|
+
.trim()
|
|
4
|
+
.toLowerCase();
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
function normalizeSurfacePagesRoot(pagesRootLike) {
|
|
8
|
+
const rawPagesRoot = String(pagesRootLike || "").trim();
|
|
9
|
+
if (!rawPagesRoot || rawPagesRoot === "/") {
|
|
10
|
+
return "";
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
return rawPagesRoot
|
|
14
|
+
.replace(/\\/g, "/")
|
|
15
|
+
.replace(/\/{2,}/g, "/")
|
|
16
|
+
.replace(/^\/+|\/+$/g, "");
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function normalizeRouteSegment(segmentLike) {
|
|
20
|
+
const segment = String(segmentLike || "").trim();
|
|
21
|
+
if (!segment) {
|
|
22
|
+
return "";
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const dynamicMatch = segment.match(/^\[([^\]]+)\]$/);
|
|
26
|
+
if (dynamicMatch) {
|
|
27
|
+
const paramName = String(dynamicMatch[1] || "").trim();
|
|
28
|
+
if (!paramName) {
|
|
29
|
+
return "";
|
|
30
|
+
}
|
|
31
|
+
return `:${paramName}`;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return segment;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function deriveSurfaceRouteBaseFromPagesRoot(pagesRootLike) {
|
|
38
|
+
const normalizedPagesRoot = normalizeSurfacePagesRoot(pagesRootLike);
|
|
39
|
+
if (!normalizedPagesRoot) {
|
|
40
|
+
return "/";
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const segments = normalizedPagesRoot
|
|
44
|
+
.split("/")
|
|
45
|
+
.map((segment) => normalizeRouteSegment(segment))
|
|
46
|
+
.filter(Boolean);
|
|
47
|
+
|
|
48
|
+
if (segments.length < 1) {
|
|
49
|
+
return "/";
|
|
50
|
+
}
|
|
51
|
+
return `/${segments.join("/")}`;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function createSurfaceRegistry(options = {}) {
|
|
55
|
+
const rawSurfaces = options?.surfaces;
|
|
56
|
+
if (!rawSurfaces || typeof rawSurfaces !== "object") {
|
|
57
|
+
throw new Error("createSurfaceRegistry requires a surfaces object.");
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const normalizedEntries = Object.entries(rawSurfaces)
|
|
61
|
+
.map(([key, value]) => {
|
|
62
|
+
const sourceDefinition = value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
63
|
+
const normalizedId = normalizeSurfaceId(sourceDefinition.id || key);
|
|
64
|
+
if (!normalizedId) {
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const pagesRoot = normalizeSurfacePagesRoot(sourceDefinition.pagesRoot);
|
|
69
|
+
if (
|
|
70
|
+
!Object.prototype.hasOwnProperty.call(sourceDefinition, "pagesRoot") &&
|
|
71
|
+
pagesRoot === ""
|
|
72
|
+
) {
|
|
73
|
+
throw new Error(`Surface "${normalizedId}" requires pagesRoot (use "" for root).`);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return [
|
|
77
|
+
normalizedId,
|
|
78
|
+
Object.freeze({
|
|
79
|
+
...sourceDefinition,
|
|
80
|
+
id: normalizedId,
|
|
81
|
+
pagesRoot,
|
|
82
|
+
routeBase: deriveSurfaceRouteBaseFromPagesRoot(pagesRoot)
|
|
83
|
+
})
|
|
84
|
+
];
|
|
85
|
+
})
|
|
86
|
+
.filter(Boolean);
|
|
87
|
+
|
|
88
|
+
if (normalizedEntries.length < 1) {
|
|
89
|
+
throw new Error("createSurfaceRegistry requires at least one surface definition.");
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const SURFACE_REGISTRY = Object.freeze(Object.fromEntries(normalizedEntries));
|
|
93
|
+
const fallbackSurfaceId = normalizedEntries[0][0];
|
|
94
|
+
const requestedDefaultSurfaceId = normalizeSurfaceId(options?.defaultSurfaceId);
|
|
95
|
+
const DEFAULT_SURFACE_ID = SURFACE_REGISTRY[requestedDefaultSurfaceId]
|
|
96
|
+
? requestedDefaultSurfaceId
|
|
97
|
+
: fallbackSurfaceId;
|
|
98
|
+
|
|
99
|
+
function normalizeRegisteredSurfaceId(value) {
|
|
100
|
+
const normalized = normalizeSurfaceId(value);
|
|
101
|
+
if (SURFACE_REGISTRY[normalized]) {
|
|
102
|
+
return normalized;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return DEFAULT_SURFACE_ID;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function resolveSurfacePagesRoot(surfaceId) {
|
|
109
|
+
return SURFACE_REGISTRY[normalizeRegisteredSurfaceId(surfaceId)]?.pagesRoot || "";
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function resolveSurfaceRouteBase(surfaceId) {
|
|
113
|
+
return SURFACE_REGISTRY[normalizeRegisteredSurfaceId(surfaceId)]?.routeBase || "/";
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function listSurfaceDefinitions() {
|
|
117
|
+
return Object.values(SURFACE_REGISTRY);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return Object.freeze({
|
|
121
|
+
SURFACE_REGISTRY,
|
|
122
|
+
DEFAULT_SURFACE_ID,
|
|
123
|
+
normalizeSurfaceId: normalizeRegisteredSurfaceId,
|
|
124
|
+
resolveSurfacePagesRoot,
|
|
125
|
+
resolveSurfaceRouteBase,
|
|
126
|
+
listSurfaceDefinitions
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export {
|
|
131
|
+
createSurfaceRegistry,
|
|
132
|
+
normalizeSurfaceId,
|
|
133
|
+
normalizeSurfacePagesRoot,
|
|
134
|
+
deriveSurfaceRouteBaseFromPagesRoot
|
|
135
|
+
};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
createSurfaceRegistry,
|
|
6
|
+
deriveSurfaceRouteBaseFromPagesRoot,
|
|
7
|
+
normalizeSurfaceId,
|
|
8
|
+
normalizeSurfacePagesRoot
|
|
9
|
+
} from "./registry.js";
|
|
10
|
+
|
|
11
|
+
test("normalizeSurfaceId lowercases and trims", () => {
|
|
12
|
+
assert.equal(normalizeSurfaceId(" Admin "), "admin");
|
|
13
|
+
assert.equal(normalizeSurfaceId(""), "");
|
|
14
|
+
assert.equal(normalizeSurfaceId(null), "");
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
test("normalizeSurfacePagesRoot normalizes slash style and root pages root", () => {
|
|
18
|
+
assert.equal(normalizeSurfacePagesRoot(" admin "), "admin");
|
|
19
|
+
assert.equal(normalizeSurfacePagesRoot("/admin///"), "admin");
|
|
20
|
+
assert.equal(normalizeSurfacePagesRoot("/"), "");
|
|
21
|
+
assert.equal(normalizeSurfacePagesRoot(""), "");
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test("deriveSurfaceRouteBaseFromPagesRoot converts dynamic segments", () => {
|
|
25
|
+
assert.equal(deriveSurfaceRouteBaseFromPagesRoot(""), "/");
|
|
26
|
+
assert.equal(deriveSurfaceRouteBaseFromPagesRoot("console"), "/console");
|
|
27
|
+
assert.equal(deriveSurfaceRouteBaseFromPagesRoot("w/[workspaceSlug]"), "/w/:workspaceSlug");
|
|
28
|
+
assert.equal(deriveSurfaceRouteBaseFromPagesRoot("w/[workspaceSlug]/admin"), "/w/:workspaceSlug/admin");
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test("createSurfaceRegistry keeps registered normalization with fallback", () => {
|
|
32
|
+
const registry = createSurfaceRegistry({
|
|
33
|
+
surfaces: {
|
|
34
|
+
app: { id: "app", pagesRoot: "" },
|
|
35
|
+
admin: { id: "admin", pagesRoot: "admin" }
|
|
36
|
+
},
|
|
37
|
+
defaultSurfaceId: "app"
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
assert.equal(registry.normalizeSurfaceId("ADMIN"), "admin");
|
|
41
|
+
assert.equal(registry.normalizeSurfaceId("unknown"), "app");
|
|
42
|
+
assert.equal(registry.resolveSurfacePagesRoot("ADMIN"), "admin");
|
|
43
|
+
assert.equal(registry.resolveSurfaceRouteBase("ADMIN"), "/admin");
|
|
44
|
+
});
|