@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.
Files changed (185) hide show
  1. package/README.md +24 -0
  2. package/_testable/index.js +4 -0
  3. package/client/appConfig.js +33 -0
  4. package/client/componentInteraction.js +51 -0
  5. package/client/componentInteraction.test.js +111 -0
  6. package/client/descriptorSections.js +75 -0
  7. package/client/index.d.ts +70 -0
  8. package/client/index.js +3 -0
  9. package/client/logging.js +38 -0
  10. package/client/moduleBootstrap.js +670 -0
  11. package/client/moduleBootstrap.test.js +403 -0
  12. package/client/shellBootstrap.js +233 -0
  13. package/client/shellBootstrap.test.js +185 -0
  14. package/client/shellRouting.js +321 -0
  15. package/client/shellRouting.test.js +113 -0
  16. package/client/vite/clientBootstrapPlugin.js +259 -0
  17. package/client/vite/clientBootstrapPlugin.test.js +563 -0
  18. package/client/vite/index.js +3 -0
  19. package/internal/node/fileSystem.js +21 -0
  20. package/internal/node/installedPackageDescriptor.js +104 -0
  21. package/package.json +43 -0
  22. package/server/actions/ActionRuntimeServiceProvider.js +309 -0
  23. package/server/actions/ActionRuntimeServiceProvider.test.js +551 -0
  24. package/server/actions/index.js +8 -0
  25. package/server/container/ContainerCoreServiceProvider.js +27 -0
  26. package/server/container/index.js +10 -0
  27. package/server/exportPolicy.test.js +68 -0
  28. package/server/http/HttpFastifyServiceProvider.js +25 -0
  29. package/server/http/_testable/index.js +2 -0
  30. package/server/http/index.js +1 -0
  31. package/server/http/lib/controller.js +183 -0
  32. package/server/http/lib/controller.test.js +143 -0
  33. package/server/http/lib/errors.js +12 -0
  34. package/server/http/lib/httpRuntime.js +82 -0
  35. package/server/http/lib/index.js +18 -0
  36. package/server/http/lib/kernel.js +15 -0
  37. package/server/http/lib/kernel.test.js +880 -0
  38. package/server/http/lib/middlewareRuntime.js +149 -0
  39. package/server/http/lib/requestActionExecutor.js +258 -0
  40. package/server/http/lib/requestScope.js +59 -0
  41. package/server/http/lib/routeRegistration.js +165 -0
  42. package/server/http/lib/routeSupport.js +45 -0
  43. package/server/http/lib/routeValidator.js +469 -0
  44. package/server/http/lib/routeValidator.test.js +474 -0
  45. package/server/http/lib/router.js +206 -0
  46. package/server/kernel/KernelCoreServiceProvider.js +27 -0
  47. package/server/kernel/index.js +10 -0
  48. package/server/platform/PlatformServerRuntimeServiceProvider.js +30 -0
  49. package/server/platform/index.js +5 -0
  50. package/server/platform/providerRuntime/descriptorCatalog.js +170 -0
  51. package/server/platform/providerRuntime/helpers.js +45 -0
  52. package/server/platform/providerRuntime/lockfile.js +27 -0
  53. package/server/platform/providerRuntime/providerLoader.js +283 -0
  54. package/server/platform/providerRuntime.js +142 -0
  55. package/server/platform/providerRuntime.test.js +217 -0
  56. package/server/platform/runtime.js +40 -0
  57. package/server/platform/surfaceRuntime.js +150 -0
  58. package/server/platform/surfaceRuntime.test.js +136 -0
  59. package/server/registries/actionSurfaceSourceRegistry.js +150 -0
  60. package/server/registries/bootstrapPayloadContributorRegistry.js +41 -0
  61. package/server/registries/domainEventListenerRegistry.js +61 -0
  62. package/server/registries/index.js +36 -0
  63. package/server/registries/primitives.js +63 -0
  64. package/server/registries/routeVisibilityResolverRegistry.js +87 -0
  65. package/server/registries/serviceRegistrationRegistry.js +431 -0
  66. package/server/runtime/ServerRuntimeCoreServiceProvider.js +65 -0
  67. package/server/runtime/ServerRuntimeCoreServiceProvider.test.js +53 -0
  68. package/server/runtime/apiRoutePolicyParity.test.js +109 -0
  69. package/server/runtime/apiRouteRegistration.js +65 -0
  70. package/server/runtime/bootBootstrapRoutes.js +46 -0
  71. package/server/runtime/bootBootstrapRoutes.test.js +79 -0
  72. package/server/runtime/bootstrapContributors.test.js +114 -0
  73. package/server/runtime/canonicalJson.js +74 -0
  74. package/server/runtime/composition.js +142 -0
  75. package/server/runtime/domainEvents.test.js +114 -0
  76. package/server/runtime/domainRules.js +50 -0
  77. package/server/runtime/domainRules.test.js +87 -0
  78. package/server/runtime/entityChangeEvents.js +182 -0
  79. package/server/runtime/entityChangeEvents.test.js +211 -0
  80. package/server/runtime/errors.js +68 -0
  81. package/server/runtime/errors.test.js +73 -0
  82. package/server/runtime/fastifyBootstrap.js +372 -0
  83. package/server/runtime/fastifyBootstrap.test.js +194 -0
  84. package/server/runtime/index.js +6 -0
  85. package/server/runtime/integers.js +13 -0
  86. package/server/runtime/moduleConfig.js +269 -0
  87. package/server/runtime/moduleConfig.test.js +141 -0
  88. package/server/runtime/pagination.js +13 -0
  89. package/server/runtime/realtimeNormalization.js +21 -0
  90. package/server/runtime/requestUrl.js +38 -0
  91. package/server/runtime/routeUtils.js +20 -0
  92. package/server/runtime/runtimeAssembly.js +113 -0
  93. package/server/runtime/runtimeKernel.js +55 -0
  94. package/server/runtime/securityAudit.js +269 -0
  95. package/server/runtime/securityAudit.test.js +41 -0
  96. package/server/runtime/serviceAuthorization.js +113 -0
  97. package/server/runtime/serviceAuthorization.test.js +100 -0
  98. package/server/runtime/serviceRegistration.test.js +197 -0
  99. package/server/support/SupportCoreServiceProvider.js +25 -0
  100. package/server/support/appConfig.js +37 -0
  101. package/server/support/appConfig.test.js +94 -0
  102. package/server/support/defaultMissingHandler.js +7 -0
  103. package/server/support/index.js +2 -0
  104. package/server/support/routePolicyConfig.js +51 -0
  105. package/server/support/symlinkSafeRequire.js +78 -0
  106. package/server/support/symlinkSafeRequire.test.js +27 -0
  107. package/server/surface/SurfaceRoutingServiceProvider.js +27 -0
  108. package/server/surface/index.js +19 -0
  109. package/shared/actions/actionContributorHelpers.js +34 -0
  110. package/shared/actions/actionContributorHelpers.test.js +16 -0
  111. package/shared/actions/actionDefinitions.js +488 -0
  112. package/shared/actions/actionDefinitions.test.js +212 -0
  113. package/shared/actions/audit.js +7 -0
  114. package/shared/actions/executionContext.js +97 -0
  115. package/shared/actions/executionContext.test.js +66 -0
  116. package/shared/actions/idempotency.js +62 -0
  117. package/shared/actions/index.js +2 -0
  118. package/shared/actions/observability.js +10 -0
  119. package/shared/actions/pipeline.js +287 -0
  120. package/shared/actions/policies.js +342 -0
  121. package/shared/actions/policies.test.js +233 -0
  122. package/shared/actions/registry.js +187 -0
  123. package/shared/actions/registry.test.js +381 -0
  124. package/shared/actions/requestMeta.js +36 -0
  125. package/shared/actions/textNormalization.js +3 -0
  126. package/shared/actions/withActionDefaults.js +34 -0
  127. package/shared/index.js +2 -0
  128. package/shared/runtime/application.js +323 -0
  129. package/shared/runtime/container.js +261 -0
  130. package/shared/runtime/containerErrors.js +22 -0
  131. package/shared/runtime/index.js +18 -0
  132. package/shared/runtime/kernelErrors.js +20 -0
  133. package/shared/runtime/serviceProvider.js +13 -0
  134. package/shared/support/formatDateTime.js +10 -0
  135. package/shared/support/formatDateTime.test.js +15 -0
  136. package/shared/support/index.js +14 -0
  137. package/shared/support/linkPath.js +67 -0
  138. package/shared/support/linkPath.test.js +35 -0
  139. package/shared/support/normalize.js +116 -0
  140. package/shared/support/normalize.test.js +48 -0
  141. package/shared/support/packageDescriptor.test.js +121 -0
  142. package/shared/support/permissions.js +50 -0
  143. package/shared/support/pickOwnProperties.js +17 -0
  144. package/shared/support/pickOwnProperties.test.js +25 -0
  145. package/shared/support/policies.js +11 -0
  146. package/shared/support/queryPath.js +33 -0
  147. package/shared/support/queryPath.test.js +19 -0
  148. package/shared/support/queryResilience.js +34 -0
  149. package/shared/support/queryResilience.test.js +33 -0
  150. package/shared/support/returnToPath.js +153 -0
  151. package/shared/support/returnToPath.test.js +123 -0
  152. package/shared/support/sorting.js +15 -0
  153. package/shared/support/tokens.js +23 -0
  154. package/shared/support/tokens.test.js +17 -0
  155. package/shared/support/visibility.js +56 -0
  156. package/shared/support/visibility.test.js +45 -0
  157. package/shared/surface/apiPaths.js +84 -0
  158. package/shared/surface/escapeRegExp.js +5 -0
  159. package/shared/surface/index.js +6 -0
  160. package/shared/surface/paths.js +273 -0
  161. package/shared/surface/registry.js +135 -0
  162. package/shared/surface/registry.test.js +44 -0
  163. package/shared/surface/runtime.js +357 -0
  164. package/shared/surface/runtime.test.js +319 -0
  165. package/shared/validators/createCursorListValidator.js +42 -0
  166. package/shared/validators/createCursorListValidator.test.js +34 -0
  167. package/shared/validators/cursorPaginationQueryValidator.js +31 -0
  168. package/shared/validators/cursorPaginationQueryValidator.test.js +21 -0
  169. package/shared/validators/index.js +12 -0
  170. package/shared/validators/inputNormalization.js +13 -0
  171. package/shared/validators/mergeObjectSchemas.js +31 -0
  172. package/shared/validators/mergeObjectSchemas.test.js +67 -0
  173. package/shared/validators/mergeValidators.js +89 -0
  174. package/shared/validators/mergeValidators.test.js +116 -0
  175. package/shared/validators/nestValidator.js +53 -0
  176. package/shared/validators/nestValidator.test.js +60 -0
  177. package/shared/validators/recordIdParamsValidator.js +36 -0
  178. package/shared/validators/recordIdParamsValidator.test.js +20 -0
  179. package/shared/validators/resourceRequiredMetadata.js +41 -0
  180. package/shared/validators/resourceRequiredMetadata.test.js +49 -0
  181. package/test/barrelExposure.test.js +106 -0
  182. package/test/dynamicImportPolicy.test.js +89 -0
  183. package/test/exportsContract.test.js +168 -0
  184. package/test/routeInputContractGuard.test.js +78 -0
  185. package/test/surfaceIndependence.test.js +109 -0
@@ -0,0 +1,153 @@
1
+ import { normalizePathname } from "../surface/paths.js";
2
+
3
+ function normalizeHttpOrigin(value = "") {
4
+ const rawValue = String(value || "").trim();
5
+ if (!rawValue) {
6
+ return "";
7
+ }
8
+
9
+ try {
10
+ const parsed = new URL(rawValue);
11
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
12
+ return "";
13
+ }
14
+ return parsed.origin;
15
+ } catch {
16
+ return "";
17
+ }
18
+ }
19
+
20
+ function normalizeAllowedOrigins(allowedOrigins = []) {
21
+ const source = Array.isArray(allowedOrigins) ? allowedOrigins : [allowedOrigins];
22
+ const normalizedOrigins = [];
23
+
24
+ for (const originValue of source) {
25
+ const normalizedOrigin = normalizeHttpOrigin(originValue);
26
+ if (!normalizedOrigin || normalizedOrigins.includes(normalizedOrigin)) {
27
+ continue;
28
+ }
29
+ normalizedOrigins.push(normalizedOrigin);
30
+ }
31
+
32
+ return normalizedOrigins;
33
+ }
34
+
35
+ function appendNormalizedOrigin(origins, value) {
36
+ const normalizedOrigin = normalizeHttpOrigin(value);
37
+ if (!normalizedOrigin || origins.includes(normalizedOrigin)) {
38
+ return;
39
+ }
40
+ origins.push(normalizedOrigin);
41
+ }
42
+
43
+ function resolveAllowedOriginsFromSurfaceDefinitions(surfaceDefinitions = {}, { seedOrigins = [] } = {}) {
44
+ const resolvedOrigins = normalizeAllowedOrigins(seedOrigins);
45
+ const source =
46
+ surfaceDefinitions && typeof surfaceDefinitions === "object" && !Array.isArray(surfaceDefinitions)
47
+ ? surfaceDefinitions
48
+ : {};
49
+
50
+ for (const definition of Object.values(source)) {
51
+ if (!definition || typeof definition !== "object") {
52
+ continue;
53
+ }
54
+
55
+ appendNormalizedOrigin(resolvedOrigins, definition.origin);
56
+ }
57
+
58
+ return resolvedOrigins;
59
+ }
60
+
61
+ function normalizeBlockedPathnames(blockedPathnames = []) {
62
+ const source = Array.isArray(blockedPathnames) ? blockedPathnames : [blockedPathnames];
63
+ const normalizedPathnames = [];
64
+
65
+ for (const pathnameValue of source) {
66
+ const rawPathname = String(pathnameValue || "").trim();
67
+ if (!rawPathname) {
68
+ continue;
69
+ }
70
+ const normalizedPathname = normalizePathname(rawPathname);
71
+ if (normalizedPathnames.includes(normalizedPathname)) {
72
+ continue;
73
+ }
74
+ normalizedPathnames.push(normalizedPathname);
75
+ }
76
+
77
+ return normalizedPathnames;
78
+ }
79
+
80
+ function resolveAllowedOriginsFromPlacementContext(contextValue = null, { includeWindowOrigin = true } = {}) {
81
+ const seedOrigins = [];
82
+
83
+ if (includeWindowOrigin && typeof window === "object" && window?.location?.origin) {
84
+ appendNormalizedOrigin(seedOrigins, window.location.origin);
85
+ }
86
+
87
+ const surfaceConfig =
88
+ contextValue && typeof contextValue === "object" && contextValue.surfaceConfig && typeof contextValue.surfaceConfig === "object"
89
+ ? contextValue.surfaceConfig
90
+ : {};
91
+ const surfacesById =
92
+ surfaceConfig.surfacesById && typeof surfaceConfig.surfacesById === "object" ? surfaceConfig.surfacesById : {};
93
+ return resolveAllowedOriginsFromSurfaceDefinitions(surfacesById, {
94
+ seedOrigins
95
+ });
96
+ }
97
+
98
+ function normalizeReturnToPath(
99
+ value,
100
+ {
101
+ fallback = "/",
102
+ allowedOrigins = [],
103
+ blockedPathnames = [],
104
+ pickFirstArrayValue = false
105
+ } = {}
106
+ ) {
107
+ const source = pickFirstArrayValue && Array.isArray(value) ? value[0] : value;
108
+ const rawValue = String(source || "").trim();
109
+ if (!rawValue) {
110
+ return fallback;
111
+ }
112
+
113
+ const normalizedBlockedPathnames = normalizeBlockedPathnames(blockedPathnames);
114
+
115
+ if (rawValue.startsWith("/") && !rawValue.startsWith("//")) {
116
+ const normalizedPathname = normalizePathname(rawValue);
117
+ if (normalizedBlockedPathnames.includes(normalizedPathname)) {
118
+ return fallback;
119
+ }
120
+ return rawValue;
121
+ }
122
+
123
+ let parsed;
124
+ try {
125
+ parsed = new URL(rawValue);
126
+ } catch {
127
+ return fallback;
128
+ }
129
+
130
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
131
+ return fallback;
132
+ }
133
+
134
+ const normalizedAllowedOrigins = normalizeAllowedOrigins(allowedOrigins);
135
+ if (normalizedAllowedOrigins.length > 0 && !normalizedAllowedOrigins.includes(parsed.origin)) {
136
+ return fallback;
137
+ }
138
+
139
+ const normalizedPathname = normalizePathname(parsed.pathname);
140
+ if (normalizedBlockedPathnames.includes(normalizedPathname)) {
141
+ return fallback;
142
+ }
143
+
144
+ return parsed.toString();
145
+ }
146
+
147
+ export {
148
+ normalizeAllowedOrigins,
149
+ normalizeHttpOrigin,
150
+ normalizeReturnToPath,
151
+ resolveAllowedOriginsFromSurfaceDefinitions,
152
+ resolveAllowedOriginsFromPlacementContext
153
+ };
@@ -0,0 +1,123 @@
1
+ import assert from "node:assert/strict";
2
+ import test from "node:test";
3
+
4
+ import {
5
+ normalizeAllowedOrigins,
6
+ normalizeHttpOrigin,
7
+ normalizeReturnToPath,
8
+ resolveAllowedOriginsFromSurfaceDefinitions,
9
+ resolveAllowedOriginsFromPlacementContext
10
+ } from "./returnToPath.js";
11
+
12
+ test("normalizeHttpOrigin keeps valid http origins only", () => {
13
+ assert.equal(normalizeHttpOrigin("https://app.example.com/path?q=1"), "https://app.example.com");
14
+ assert.equal(normalizeHttpOrigin("http://localhost:3000"), "http://localhost:3000");
15
+ assert.equal(normalizeHttpOrigin("javascript:alert(1)"), "");
16
+ });
17
+
18
+ test("normalizeAllowedOrigins deduplicates and filters invalid origins", () => {
19
+ assert.deepEqual(normalizeAllowedOrigins([
20
+ "https://app.example.com",
21
+ "https://app.example.com/console",
22
+ "http://localhost:5173",
23
+ "",
24
+ "ftp://files.example.com"
25
+ ]), [
26
+ "https://app.example.com",
27
+ "http://localhost:5173"
28
+ ]);
29
+ });
30
+
31
+ test("normalizeReturnToPath allows internal paths and permitted absolute urls", () => {
32
+ assert.equal(normalizeReturnToPath("/w/acme/projects?tab=active", {
33
+ fallback: "/"
34
+ }), "/w/acme/projects?tab=active");
35
+ assert.equal(normalizeReturnToPath("https://app.example.com/w/acme", {
36
+ fallback: "/",
37
+ allowedOrigins: ["https://app.example.com"]
38
+ }), "https://app.example.com/w/acme");
39
+ });
40
+
41
+ test("normalizeReturnToPath rejects blocked paths, invalid schemes, and untrusted origins", () => {
42
+ assert.equal(normalizeReturnToPath("/auth/login?returnTo=%2F", {
43
+ fallback: "/",
44
+ blockedPathnames: ["/auth/login"]
45
+ }), "/");
46
+ assert.equal(normalizeReturnToPath("https://auth.example.com/auth/signout", {
47
+ fallback: "/",
48
+ allowedOrigins: ["https://auth.example.com"],
49
+ blockedPathnames: ["/auth/signout"]
50
+ }), "/");
51
+ assert.equal(normalizeReturnToPath("https://evil.example.com/phishing", {
52
+ fallback: "/",
53
+ allowedOrigins: ["https://app.example.com"]
54
+ }), "/");
55
+ assert.equal(normalizeReturnToPath("javascript:alert(1)", {
56
+ fallback: "/"
57
+ }), "/");
58
+ assert.equal(normalizeReturnToPath("/account", {
59
+ fallback: "/",
60
+ blockedPathnames: ["account"]
61
+ }), "/");
62
+ });
63
+
64
+ test("normalizeReturnToPath can read first value from array payloads", () => {
65
+ assert.equal(normalizeReturnToPath(["/dashboard", "/ignored"], {
66
+ fallback: "/",
67
+ pickFirstArrayValue: true
68
+ }), "/dashboard");
69
+ });
70
+
71
+ test("resolveAllowedOriginsFromSurfaceDefinitions includes seed origins and surface origins", () => {
72
+ const origins = resolveAllowedOriginsFromSurfaceDefinitions(
73
+ {
74
+ home: { origin: "https://home.example.com" },
75
+ app: { origin: "https://app.example.com" },
76
+ duplicate: { origin: "https://home.example.com" },
77
+ invalid: { origin: "mailto:hello@example.com" }
78
+ },
79
+ {
80
+ seedOrigins: ["https://public.example.com", "https://home.example.com"]
81
+ }
82
+ );
83
+
84
+ assert.deepEqual(origins, [
85
+ "https://public.example.com",
86
+ "https://home.example.com",
87
+ "https://app.example.com"
88
+ ]);
89
+ });
90
+
91
+ test("resolveAllowedOriginsFromPlacementContext collects current and surface origins", () => {
92
+ const originalWindow = globalThis.window;
93
+ globalThis.window = {
94
+ location: {
95
+ origin: "https://current.example.com"
96
+ }
97
+ };
98
+
99
+ try {
100
+ const origins = resolveAllowedOriginsFromPlacementContext({
101
+ surfaceConfig: {
102
+ surfacesById: {
103
+ home: { origin: "https://home.example.com" },
104
+ app: { origin: "https://app.example.com" },
105
+ duplicate: { origin: "https://home.example.com" },
106
+ invalid: { origin: "mailto:hello@example.com" }
107
+ }
108
+ }
109
+ });
110
+
111
+ assert.deepEqual(origins, [
112
+ "https://current.example.com",
113
+ "https://home.example.com",
114
+ "https://app.example.com"
115
+ ]);
116
+ } finally {
117
+ if (typeof originalWindow === "undefined") {
118
+ delete globalThis.window;
119
+ } else {
120
+ globalThis.window = originalWindow;
121
+ }
122
+ }
123
+ });
@@ -0,0 +1,15 @@
1
+ function sortStrings(values = []) {
2
+ return [...new Set((Array.isArray(values) ? values : []).map((value) => String(value || "").trim()).filter(Boolean))].sort(
3
+ (left, right) => left.localeCompare(right)
4
+ );
5
+ }
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 };
@@ -0,0 +1,23 @@
1
+ const KERNEL_TOKENS = Object.freeze({
2
+ Logger: Symbol.for("jskit.logger"),
3
+ Env: Symbol.for("jskit.env"),
4
+ Fastify: Symbol.for("jskit.fastify"),
5
+ HttpRouter: Symbol.for("jskit.http.router"),
6
+ Request: Symbol.for("jskit.http.request"),
7
+ Reply: Symbol.for("jskit.http.reply"),
8
+ RequestId: Symbol.for("jskit.http.requestId"),
9
+ RequestScope: Symbol.for("jskit.http.requestScope"),
10
+ Knex: Symbol.for("jskit.database.knex"),
11
+ TransactionManager: Symbol.for("jskit.database.transactionManager"),
12
+ Storage: Symbol.for("jskit.storage"),
13
+ SurfaceRuntime: Symbol.for("jskit.surface.runtime")
14
+ });
15
+
16
+ function isContainerToken(value) {
17
+ if (typeof value === "string") {
18
+ return value.trim().length > 0;
19
+ }
20
+ return typeof value === "symbol" || typeof value === "function";
21
+ }
22
+
23
+ export { KERNEL_TOKENS, isContainerToken };
@@ -0,0 +1,17 @@
1
+ import test from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { isContainerToken } from "./tokens.js";
4
+
5
+ test("isContainerToken accepts valid container token types", () => {
6
+ assert.equal(isContainerToken("appConfig"), true);
7
+ assert.equal(isContainerToken(Symbol.for("jskit.test")), true);
8
+ assert.equal(isContainerToken(() => {}), true);
9
+ });
10
+
11
+ test("isContainerToken rejects empty and unsupported token values", () => {
12
+ assert.equal(isContainerToken(""), false);
13
+ assert.equal(isContainerToken(" "), false);
14
+ assert.equal(isContainerToken(null), false);
15
+ assert.equal(isContainerToken(123), false);
16
+ assert.equal(isContainerToken({}), false);
17
+ });
@@ -0,0 +1,56 @@
1
+ import { normalizeOpaqueId, normalizeText } from "./normalize.js";
2
+ import { ROUTE_VISIBILITY_PUBLIC, ROUTE_VISIBILITY_USER } from "./policies.js";
3
+
4
+ const ROUTE_VISIBILITY_LEVELS = Object.freeze([ROUTE_VISIBILITY_PUBLIC, ROUTE_VISIBILITY_USER]);
5
+ const ROUTE_VISIBILITY_LEVEL_SET = new Set(ROUTE_VISIBILITY_LEVELS);
6
+
7
+ function normalizeRouteVisibilityToken(value, { fallback = ROUTE_VISIBILITY_PUBLIC } = {}) {
8
+ const normalized = normalizeText(value).toLowerCase();
9
+ if (normalized) {
10
+ return normalized;
11
+ }
12
+
13
+ const normalizedFallback = normalizeText(fallback).toLowerCase();
14
+ if (normalizedFallback) {
15
+ return normalizedFallback;
16
+ }
17
+
18
+ return ROUTE_VISIBILITY_PUBLIC;
19
+ }
20
+
21
+ function normalizeRouteVisibility(value, { fallback = ROUTE_VISIBILITY_PUBLIC } = {}) {
22
+ const normalized = normalizeRouteVisibilityToken(value, { fallback });
23
+ if (ROUTE_VISIBILITY_LEVEL_SET.has(normalized)) {
24
+ return normalized;
25
+ }
26
+
27
+ const normalizedFallback = normalizeRouteVisibilityToken(fallback, {
28
+ fallback: ROUTE_VISIBILITY_PUBLIC
29
+ });
30
+ if (ROUTE_VISIBILITY_LEVEL_SET.has(normalizedFallback)) {
31
+ return normalizedFallback;
32
+ }
33
+
34
+ return ROUTE_VISIBILITY_PUBLIC;
35
+ }
36
+
37
+ function normalizeVisibilityScopeKind(value) {
38
+ const normalized = normalizeText(value).toLowerCase();
39
+ return normalized || null;
40
+ }
41
+
42
+ function normalizeVisibilityContext(value = {}) {
43
+ const source = value && typeof value === "object" && !Array.isArray(value) ? value : {};
44
+ const normalizedVisibility = normalizeRouteVisibilityToken(source.visibility);
45
+ const normalizedScopeKind = normalizeVisibilityScopeKind(source.scopeKind);
46
+
47
+ return Object.freeze({
48
+ visibility: normalizedVisibility,
49
+ scopeKind: normalizedScopeKind,
50
+ requiresActorScope: source.requiresActorScope === true,
51
+ scopeOwnerId: normalizeOpaqueId(source.scopeOwnerId),
52
+ userOwnerId: normalizeOpaqueId(source.userOwnerId)
53
+ });
54
+ }
55
+
56
+ export { ROUTE_VISIBILITY_LEVELS, normalizeRouteVisibilityToken, normalizeRouteVisibility, normalizeVisibilityContext };
@@ -0,0 +1,45 @@
1
+ import assert from "node:assert/strict";
2
+ import test from "node:test";
3
+ import { normalizeRouteVisibilityToken, normalizeRouteVisibility, normalizeVisibilityContext } from "./visibility.js";
4
+
5
+ test("normalizeRouteVisibility keeps kernel core visibility contract", () => {
6
+ assert.equal(normalizeRouteVisibility("PUBLIC"), "public");
7
+ assert.equal(normalizeRouteVisibility("user"), "user");
8
+ assert.equal(normalizeRouteVisibility("workspace"), "public");
9
+ assert.equal(normalizeRouteVisibility("invalid"), "public");
10
+ assert.equal(normalizeRouteVisibility("invalid", { fallback: "user" }), "user");
11
+ assert.equal(normalizeRouteVisibility(""), "public");
12
+ });
13
+
14
+ test("normalizeRouteVisibilityToken normalizes visibility tokens for module-level resolvers", () => {
15
+ assert.equal(normalizeRouteVisibilityToken("workspace"), "workspace");
16
+ assert.equal(normalizeRouteVisibilityToken("WORKSPACE_USER"), "workspace_user");
17
+ assert.equal(normalizeRouteVisibilityToken(""), "public");
18
+ assert.equal(normalizeRouteVisibilityToken("", { fallback: "workspace" }), "workspace");
19
+ });
20
+
21
+ test("normalizeVisibilityContext normalizes mode and owner identifiers", () => {
22
+ assert.deepEqual(normalizeVisibilityContext({ visibility: "user", userOwnerId: "7" }), {
23
+ visibility: "user",
24
+ scopeKind: null,
25
+ requiresActorScope: false,
26
+ scopeOwnerId: null,
27
+ userOwnerId: "7"
28
+ });
29
+
30
+ assert.deepEqual(normalizeVisibilityContext({ visibility: "workspace_user", scopeOwnerId: "4", userOwnerId: 9 }), {
31
+ visibility: "workspace_user",
32
+ scopeKind: null,
33
+ requiresActorScope: false,
34
+ scopeOwnerId: "4",
35
+ userOwnerId: 9
36
+ });
37
+
38
+ assert.deepEqual(normalizeVisibilityContext({ visibility: "workspace", scopeOwnerId: "0" }), {
39
+ visibility: "workspace",
40
+ scopeKind: null,
41
+ requiresActorScope: false,
42
+ scopeOwnerId: "0",
43
+ userOwnerId: null
44
+ });
45
+ });
@@ -0,0 +1,84 @@
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]+(?:$|\/)/;
9
+
10
+ function isApiPath(pathname) {
11
+ return matchesPathPrefix(pathname, API_BASE_PATH);
12
+ }
13
+
14
+ function isVersionedApiPath(pathname) {
15
+ return VERSIONED_API_PATH_PATTERN.test(normalizePathname(pathname));
16
+ }
17
+
18
+ function toVersionedApiPath(pathname) {
19
+ const normalizedPathname = normalizePathname(pathname);
20
+ if (!isApiPath(normalizedPathname)) {
21
+ return normalizedPathname;
22
+ }
23
+
24
+ if (isVersionedApiPath(normalizedPathname)) {
25
+ return normalizedPathname;
26
+ }
27
+
28
+ if (normalizedPathname === API_BASE_PATH) {
29
+ return API_PREFIX;
30
+ }
31
+
32
+ return `${API_PREFIX}${normalizedPathname.slice(API_BASE_PATH.length)}`;
33
+ }
34
+
35
+ function toVersionedApiPrefix(pathnameOrPrefix) {
36
+ const versionedPath = toVersionedApiPath(pathnameOrPrefix || API_BASE_PATH);
37
+ if (!isApiPath(versionedPath)) {
38
+ return normalizePathname(versionedPath);
39
+ }
40
+
41
+ if (versionedPath === API_PREFIX) {
42
+ return API_PREFIX_SLASH;
43
+ }
44
+
45
+ return `${versionedPath}/`;
46
+ }
47
+
48
+ function buildVersionedApiPath(suffix) {
49
+ const rawSuffix = String(suffix || "").trim();
50
+ if (!rawSuffix || rawSuffix === "/") {
51
+ return API_PREFIX;
52
+ }
53
+
54
+ const normalizedSuffix = normalizePathname(rawSuffix.startsWith("/") ? rawSuffix : `/${rawSuffix}`);
55
+ if (isApiPath(normalizedSuffix)) {
56
+ return toVersionedApiPath(normalizedSuffix);
57
+ }
58
+
59
+ if (normalizedSuffix === "/") {
60
+ return API_PREFIX;
61
+ }
62
+
63
+ return `${API_PREFIX}${normalizedSuffix}`;
64
+ }
65
+
66
+ function isVersionedApiPrefixMatch(pathname) {
67
+ const normalizedPathname = normalizePathname(pathname);
68
+ return normalizedPathname === API_PREFIX || normalizedPathname.startsWith(API_PREFIX_SLASH);
69
+ }
70
+
71
+ export {
72
+ API_BASE_PATH,
73
+ API_PREFIX,
74
+ API_PREFIX_SLASH,
75
+ API_DOCS_PATH,
76
+ API_REALTIME_PATH,
77
+ normalizePathname,
78
+ isApiPath,
79
+ isVersionedApiPath,
80
+ toVersionedApiPath,
81
+ toVersionedApiPrefix,
82
+ buildVersionedApiPath,
83
+ isVersionedApiPrefixMatch
84
+ };
@@ -0,0 +1,5 @@
1
+ function escapeRegExp(value) {
2
+ return String(value || "").replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3
+ }
4
+
5
+ export { escapeRegExp };
@@ -0,0 +1,6 @@
1
+ export {
2
+ normalizeSurfaceId,
3
+ normalizeSurfacePagesRoot,
4
+ deriveSurfaceRouteBaseFromPagesRoot
5
+ } from "./registry.js";
6
+ export { createSurfacePathHelpers } from "./paths.js";