@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,25 @@
1
+ import * as normalize from "../../shared/support/normalize.js";
2
+ import * as sorting from "../../shared/support/sorting.js";
3
+ import * as tokens from "../../shared/support/tokens.js";
4
+
5
+ const SUPPORT_CORE_API = Object.freeze({
6
+ normalize: Object.freeze({ ...normalize }),
7
+ sorting: Object.freeze({ ...sorting }),
8
+ tokens: Object.freeze({ ...tokens })
9
+ });
10
+
11
+ class SupportCoreServiceProvider {
12
+ static id = "runtime.support";
13
+
14
+ register(app) {
15
+ if (!app || typeof app.singleton !== "function") {
16
+ throw new Error("SupportCoreServiceProvider requires application singleton().");
17
+ }
18
+
19
+ app.singleton("runtime.support", () => SUPPORT_CORE_API);
20
+ }
21
+
22
+ boot() {}
23
+ }
24
+
25
+ export { SupportCoreServiceProvider };
@@ -0,0 +1,37 @@
1
+ import { normalizeObject } from "../../shared/support/normalize.js";
2
+ import { normalizeSurfaceId } from "../../shared/surface/registry.js";
3
+
4
+ function resolveAppConfig(scope = null) {
5
+ const source = scope && typeof scope === "object" ? scope : null;
6
+ if (!source || typeof source.has !== "function" || typeof source.make !== "function") {
7
+ return {};
8
+ }
9
+ if (!source.has("appConfig")) {
10
+ return {};
11
+ }
12
+
13
+ return normalizeObject(source.make("appConfig"));
14
+ }
15
+
16
+ function normalizeDefaultSurfaceId(value, { fallback = "" } = {}) {
17
+ const normalizedValue = normalizeSurfaceId(value);
18
+ if (normalizedValue) {
19
+ return normalizedValue;
20
+ }
21
+
22
+ const normalizedFallback = normalizeSurfaceId(fallback);
23
+ if (normalizedFallback) {
24
+ return normalizedFallback;
25
+ }
26
+
27
+ return "";
28
+ }
29
+
30
+ function resolveDefaultSurfaceId(scope = null, { defaultSurfaceId = "" } = {}) {
31
+ const appConfig = resolveAppConfig(scope);
32
+ return normalizeDefaultSurfaceId(defaultSurfaceId, {
33
+ fallback: appConfig.surfaceDefaultId
34
+ });
35
+ }
36
+
37
+ export { resolveAppConfig, normalizeDefaultSurfaceId, resolveDefaultSurfaceId };
@@ -0,0 +1,94 @@
1
+ import assert from "node:assert/strict";
2
+ import test from "node:test";
3
+ import {
4
+ resolveAppConfig,
5
+ normalizeDefaultSurfaceId,
6
+ resolveDefaultSurfaceId
7
+ } from "./appConfig.js";
8
+
9
+ test("resolveAppConfig returns normalized appConfig when scope exposes appConfig binding", () => {
10
+ const config = resolveAppConfig({
11
+ has(token) {
12
+ return token === "appConfig";
13
+ },
14
+ make(token) {
15
+ assert.equal(token, "appConfig");
16
+ return {
17
+ surfaceDefaultId: "home"
18
+ };
19
+ }
20
+ });
21
+
22
+ assert.deepEqual(config, {
23
+ surfaceDefaultId: "home"
24
+ });
25
+ });
26
+
27
+ test("resolveAppConfig returns empty object when scope has no appConfig binding", () => {
28
+ const config = resolveAppConfig({
29
+ has() {
30
+ return false;
31
+ },
32
+ make() {
33
+ throw new Error("make should not be called");
34
+ }
35
+ });
36
+
37
+ assert.deepEqual(config, {});
38
+ });
39
+
40
+ test("resolveAppConfig returns empty object for non-container values", () => {
41
+ assert.deepEqual(resolveAppConfig(null), {});
42
+ assert.deepEqual(resolveAppConfig({}), {});
43
+ assert.deepEqual(resolveAppConfig({ has: () => true }), {});
44
+ });
45
+
46
+ test("normalizeDefaultSurfaceId normalizes explicit values and fallback values", () => {
47
+ assert.equal(normalizeDefaultSurfaceId(" HOME "), "home");
48
+ assert.equal(normalizeDefaultSurfaceId("", { fallback: " Console " }), "console");
49
+ assert.equal(normalizeDefaultSurfaceId("", { fallback: "" }), "");
50
+ });
51
+
52
+ test("resolveDefaultSurfaceId prefers explicit default surface", () => {
53
+ const surfaceId = resolveDefaultSurfaceId(
54
+ {
55
+ has(token) {
56
+ return token === "appConfig";
57
+ },
58
+ make() {
59
+ return {
60
+ surfaceDefaultId: "admin"
61
+ };
62
+ }
63
+ },
64
+ {
65
+ defaultSurfaceId: "home"
66
+ }
67
+ );
68
+
69
+ assert.equal(surfaceId, "home");
70
+ });
71
+
72
+ test("resolveDefaultSurfaceId falls back to appConfig and then empty default", () => {
73
+ const fromAppConfig = resolveDefaultSurfaceId({
74
+ has(token) {
75
+ return token === "appConfig";
76
+ },
77
+ make() {
78
+ return {
79
+ surfaceDefaultId: "console"
80
+ };
81
+ }
82
+ });
83
+ assert.equal(fromAppConfig, "console");
84
+
85
+ const fromKernelFallback = resolveDefaultSurfaceId({
86
+ has() {
87
+ return false;
88
+ },
89
+ make() {
90
+ throw new Error("make should not be called");
91
+ }
92
+ });
93
+ assert.equal(fromKernelFallback, "");
94
+ });
@@ -0,0 +1,7 @@
1
+ function defaultMissingHandler(_request, reply) {
2
+ reply.code(501).send({
3
+ error: "Route handler is not available in this runtime profile."
4
+ });
5
+ }
6
+
7
+ export { defaultMissingHandler };
@@ -0,0 +1,2 @@
1
+ export { symlinkSafeRequire } from "./symlinkSafeRequire.js";
2
+ export { resolveAppConfig } from "./appConfig.js";
@@ -0,0 +1,51 @@
1
+ import { normalizeRouteVisibilityToken } from "../../shared/support/visibility.js";
2
+ import { isRecord } from "../../shared/support/normalize.js";
3
+
4
+ function normalizeRoutePolicyConfig(routeOptions, route) {
5
+ const sourceRouteOptions = isRecord(routeOptions) ? routeOptions : {};
6
+ const sourceConfig = isRecord(sourceRouteOptions.config) ? sourceRouteOptions.config : {};
7
+ const sourceRoute = isRecord(route) ? route : {};
8
+
9
+ const nextConfig = {
10
+ ...sourceConfig
11
+ };
12
+
13
+ if (Object.prototype.hasOwnProperty.call(sourceRoute, "auth")) {
14
+ nextConfig.authPolicy = sourceRoute.auth;
15
+ }
16
+ if (Object.prototype.hasOwnProperty.call(sourceRoute, "contextPolicy")) {
17
+ nextConfig.contextPolicy = sourceRoute.contextPolicy;
18
+ }
19
+ if (Object.prototype.hasOwnProperty.call(sourceRoute, "surface")) {
20
+ nextConfig.surface = sourceRoute.surface;
21
+ }
22
+ if (Object.prototype.hasOwnProperty.call(sourceRoute, "visibility")) {
23
+ nextConfig.visibility = normalizeRouteVisibilityToken(sourceRoute.visibility);
24
+ }
25
+ if (Object.prototype.hasOwnProperty.call(sourceRoute, "permission")) {
26
+ nextConfig.permission = sourceRoute.permission;
27
+ }
28
+ if (Object.prototype.hasOwnProperty.call(sourceRoute, "ownerParam")) {
29
+ nextConfig.ownerParam = sourceRoute.ownerParam;
30
+ }
31
+ if (Object.prototype.hasOwnProperty.call(sourceRoute, "userField")) {
32
+ nextConfig.userField = sourceRoute.userField;
33
+ }
34
+ if (Object.prototype.hasOwnProperty.call(sourceRoute, "ownerResolver")) {
35
+ nextConfig.ownerResolver = sourceRoute.ownerResolver;
36
+ }
37
+ if (Object.prototype.hasOwnProperty.call(sourceRoute, "csrfProtection")) {
38
+ nextConfig.csrfProtection = sourceRoute.csrfProtection;
39
+ }
40
+
41
+ return nextConfig;
42
+ }
43
+
44
+ function defaultApplyRoutePolicy(routeOptions, route) {
45
+ return {
46
+ ...routeOptions,
47
+ config: normalizeRoutePolicyConfig(routeOptions, route)
48
+ };
49
+ }
50
+
51
+ export { normalizeRoutePolicyConfig, defaultApplyRoutePolicy };
@@ -0,0 +1,78 @@
1
+ import { createRequire } from "node:module";
2
+ import path from "node:path";
3
+
4
+ function createRequireFromTarget(targetPath) {
5
+ const normalized = String(targetPath || "").trim();
6
+ if (!normalized) {
7
+ return null;
8
+ }
9
+
10
+ try {
11
+ return createRequire(normalized);
12
+ } catch {
13
+ return null;
14
+ }
15
+ }
16
+
17
+ function createAppRootRequire() {
18
+ const cwd = String(process.cwd() || "").trim();
19
+ if (!cwd) {
20
+ return null;
21
+ }
22
+
23
+ return createRequireFromTarget(path.join(cwd, "package.json"));
24
+ }
25
+
26
+ function createEntryScriptRequire() {
27
+ const entryScriptPath = String(process.argv?.[1] || "").trim();
28
+ if (!entryScriptPath) {
29
+ return null;
30
+ }
31
+
32
+ return createRequireFromTarget(path.resolve(entryScriptPath));
33
+ }
34
+
35
+ const localRequire = createRequire(import.meta.url);
36
+
37
+ function uniqueRequireChain(candidates = []) {
38
+ const chain = [];
39
+ for (const candidate of candidates) {
40
+ if (typeof candidate !== "function") {
41
+ continue;
42
+ }
43
+ if (chain.includes(candidate)) {
44
+ continue;
45
+ }
46
+ chain.push(candidate);
47
+ }
48
+ return chain;
49
+ }
50
+
51
+ function symlinkSafeRequire(moduleId = "") {
52
+ const normalizedModuleId = String(moduleId || "").trim();
53
+ if (!normalizedModuleId) {
54
+ throw new TypeError("symlinkSafeRequire requires a non-empty module id.");
55
+ }
56
+
57
+ const requireChain = uniqueRequireChain([createAppRootRequire(), createEntryScriptRequire(), localRequire]);
58
+ let lastModuleNotFoundError = null;
59
+
60
+ for (const candidateRequire of requireChain) {
61
+ try {
62
+ return candidateRequire(normalizedModuleId);
63
+ } catch (error) {
64
+ if (String(error?.code || "").trim() === "MODULE_NOT_FOUND") {
65
+ lastModuleNotFoundError = error;
66
+ continue;
67
+ }
68
+ throw error;
69
+ }
70
+ }
71
+
72
+ if (lastModuleNotFoundError) {
73
+ throw lastModuleNotFoundError;
74
+ }
75
+ throw new Error(`Failed to resolve module "${normalizedModuleId}".`);
76
+ }
77
+
78
+ export { symlinkSafeRequire };
@@ -0,0 +1,27 @@
1
+ import assert from "node:assert/strict";
2
+ import { mkdtemp, mkdir, writeFile } from "node:fs/promises";
3
+ import path from "node:path";
4
+ import { tmpdir } from "node:os";
5
+ import test from "node:test";
6
+ import { symlinkSafeRequire } from "./symlinkSafeRequire.js";
7
+
8
+ test("symlinkSafeRequire requires a non-empty module id", () => {
9
+ assert.throws(() => symlinkSafeRequire(""), /requires a non-empty module id/);
10
+ });
11
+
12
+ test("symlinkSafeRequire resolves modules from app cwd node_modules", async () => {
13
+ const appRoot = await mkdtemp(path.join(tmpdir(), "jskit-symlink-safe-require-"));
14
+ const knexPackageDir = path.join(appRoot, "node_modules", "knex");
15
+ await mkdir(knexPackageDir, { recursive: true });
16
+ await writeFile(path.join(knexPackageDir, "package.json"), JSON.stringify({ name: "knex", main: "index.js" }), "utf8");
17
+ await writeFile(path.join(knexPackageDir, "index.js"), "module.exports = { source: 'app-cwd' };", "utf8");
18
+
19
+ const previousCwd = process.cwd();
20
+ try {
21
+ process.chdir(appRoot);
22
+ const loaded = symlinkSafeRequire("knex");
23
+ assert.equal(loaded?.source, "app-cwd");
24
+ } finally {
25
+ process.chdir(previousCwd);
26
+ }
27
+ });
@@ -0,0 +1,27 @@
1
+ import * as apiPaths from "../../shared/surface/apiPaths.js";
2
+ import * as paths from "../../shared/surface/paths.js";
3
+ import * as registry from "../../shared/surface/registry.js";
4
+ import { escapeRegExp } from "../../shared/surface/escapeRegExp.js";
5
+
6
+ const SURFACE_ROUTING_API = Object.freeze({
7
+ apiPaths: Object.freeze({ ...apiPaths }),
8
+ paths: Object.freeze({ ...paths }),
9
+ registry: Object.freeze({ ...registry }),
10
+ escapeRegExp
11
+ });
12
+
13
+ class SurfaceRoutingServiceProvider {
14
+ static id = "runtime.surface-routing";
15
+
16
+ register(app) {
17
+ if (!app || typeof app.singleton !== "function") {
18
+ throw new Error("SurfaceRoutingServiceProvider requires application singleton().");
19
+ }
20
+
21
+ app.singleton("runtime.surface-routing", () => SURFACE_ROUTING_API);
22
+ }
23
+
24
+ boot() {}
25
+ }
26
+
27
+ export { SurfaceRoutingServiceProvider };
@@ -0,0 +1,19 @@
1
+ export { createSurfaceRegistry, normalizeSurfaceId } from "../../shared/surface/registry.js";
2
+ export { createSurfacePathHelpers } from "../../shared/surface/paths.js";
3
+ export { createSurfaceRuntime, filterRoutesBySurface } from "../../shared/surface/runtime.js";
4
+ export { escapeRegExp } from "../../shared/surface/escapeRegExp.js";
5
+ export {
6
+ API_BASE_PATH,
7
+ API_PREFIX,
8
+ API_PREFIX_SLASH,
9
+ API_DOCS_PATH,
10
+ API_REALTIME_PATH,
11
+ normalizePathname,
12
+ isApiPath,
13
+ isVersionedApiPath,
14
+ toVersionedApiPath,
15
+ toVersionedApiPrefix,
16
+ buildVersionedApiPath,
17
+ isVersionedApiPrefixMatch
18
+ } from "../../shared/surface/apiPaths.js";
19
+ export { SurfaceRoutingServiceProvider } from "./SurfaceRoutingServiceProvider.js";
@@ -0,0 +1,34 @@
1
+ import { Type } from "typebox";
2
+ import { normalizeObject, normalizePositiveInteger as toPositiveInteger } from "../support/normalize.js";
3
+ import { hasPermission } from "../support/permissions.js";
4
+
5
+ function requireServiceMethod(service, methodName, contributorId, { serviceLabel } = {}) {
6
+ if (!service || typeof service[methodName] !== "function") {
7
+ const prefix = serviceLabel ? `${serviceLabel}.` : "";
8
+ throw new Error(`${contributorId} requires ${prefix}${methodName}().`);
9
+ }
10
+ }
11
+
12
+ function resolveRequest(context) {
13
+ return context?.requestMeta?.request || null;
14
+ }
15
+
16
+ const OBJECT_INPUT_VALIDATOR = Object.freeze({
17
+ parse(value) {
18
+ return normalizeObject(value);
19
+ }
20
+ });
21
+
22
+ const EMPTY_INPUT_VALIDATOR = Object.freeze({
23
+ schema: Type.Object({}, { additionalProperties: false })
24
+ });
25
+
26
+ export {
27
+ normalizeObject,
28
+ toPositiveInteger,
29
+ requireServiceMethod,
30
+ resolveRequest,
31
+ hasPermission,
32
+ EMPTY_INPUT_VALIDATOR,
33
+ OBJECT_INPUT_VALIDATOR
34
+ };
@@ -0,0 +1,16 @@
1
+ import assert from "node:assert/strict";
2
+ import test from "node:test";
3
+
4
+ import { hasPermission } from "./actionContributorHelpers.js";
5
+
6
+ test("hasPermission allows wildcard and direct matches", () => {
7
+ assert.equal(hasPermission(["*"], "workspace.billing.manage"), true);
8
+ assert.equal(hasPermission(["workspace.billing.manage"], "workspace.billing.manage"), true);
9
+ assert.equal(hasPermission(["workspace.billing.*"], "workspace.billing.manage"), true);
10
+ assert.equal(hasPermission(["workspace.billing.read"], "workspace.billing.manage"), false);
11
+ });
12
+
13
+ test("hasPermission allows empty required permissions", () => {
14
+ assert.equal(hasPermission([], ""), true);
15
+ assert.equal(hasPermission([], null), true);
16
+ });