@jskit-ai/kernel 0.1.37 → 0.1.39

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/client/index.d.ts CHANGED
@@ -34,6 +34,7 @@ export function bootstrapClientShellApp(options?: {
34
34
  rootComponent: any;
35
35
  appConfig?: Record<string, any>;
36
36
  appPlugins?: any[];
37
+ pinia?: any;
37
38
  router: any;
38
39
  bootClientModules: (context: any) => Promise<any>;
39
40
  surfaceRuntime: any;
@@ -482,6 +482,7 @@ function normalizeClientModuleEntries(clientModules) {
482
482
  function createClientRuntimeApp({
483
483
  profile = "client",
484
484
  app,
485
+ pinia = null,
485
486
  router,
486
487
  env,
487
488
  logger,
@@ -496,6 +497,7 @@ function createClientRuntimeApp({
496
497
  runtimeApp.instance("jskit.client.runtime.app", runtimeApp);
497
498
  runtimeApp.instance("jskit.client.router", router || null);
498
499
  runtimeApp.instance("jskit.client.vue.app", app || null);
500
+ runtimeApp.instance("jskit.client.pinia", pinia);
499
501
  runtimeApp.instance("jskit.client.env", isRecord(env) ? { ...env } : {});
500
502
  runtimeApp.instance("jskit.client.surface.runtime", surfaceRuntime || null);
501
503
  runtimeApp.instance("jskit.client.surface.mode", String(surfaceMode || "").trim());
@@ -507,6 +509,7 @@ function createClientRuntimeApp({
507
509
  async function bootClientModules({
508
510
  clientModules = [],
509
511
  app,
512
+ pinia = null,
510
513
  router,
511
514
  surfaceRuntime,
512
515
  surfaceMode,
@@ -525,6 +528,7 @@ async function bootClientModules({
525
528
  const runtimeApp = createClientRuntimeApp({
526
529
  profile: String(surfaceRuntime.normalizeSurfaceMode(surfaceMode) || "client"),
527
530
  app,
531
+ pinia,
528
532
  router,
529
533
  env,
530
534
  logger: log,
@@ -112,11 +112,14 @@ test("bootClientModules registers descriptor and clientRoutes with providers onl
112
112
  const surfaceRuntime = createSurfaceRuntimeFixture();
113
113
  const events = [];
114
114
  const loginComponent = {};
115
+ const pinia = { id: "pinia-instance" };
116
+ const implicitPinia = { id: "implicit-vue-global-pinia" };
115
117
  class ExampleClientProvider {
116
118
  static id = "example.client";
117
119
  register(app) {
118
120
  events.push("register");
119
121
  app.instance("example.value", 42);
122
+ app.instance("example.pinia", app.make("jskit.client.pinia"));
120
123
  }
121
124
  boot() {
122
125
  events.push("boot");
@@ -179,6 +182,14 @@ test("bootClientModules registers descriptor and clientRoutes with providers onl
179
182
  }
180
183
  }
181
184
  ],
185
+ app: {
186
+ config: {
187
+ globalProperties: {
188
+ $pinia: implicitPinia
189
+ }
190
+ }
191
+ },
192
+ pinia,
182
193
  router,
183
194
  surfaceRuntime,
184
195
  surfaceMode: "all",
@@ -194,6 +205,8 @@ test("bootClientModules registers descriptor and clientRoutes with providers onl
194
205
  assert.equal(router.routes[1].path, "/auth/login");
195
206
  assert.equal(router.routes[1].component, loginComponent);
196
207
  assert.equal(result.runtimeApp.make("example.value"), 42);
208
+ assert.equal(result.runtimeApp.make("example.pinia"), pinia);
209
+ assert.notEqual(result.runtimeApp.make("example.pinia"), implicitPinia);
197
210
  });
198
211
 
199
212
  test("bootClientModules does not auto-discover providers from module exports", async () => {
@@ -108,6 +108,7 @@ async function bootstrapClientShellApp({
108
108
  rootComponent,
109
109
  appConfig = {},
110
110
  appPlugins = [],
111
+ pinia = null,
111
112
  router,
112
113
  bootClientModules,
113
114
  surfaceRuntime,
@@ -158,6 +159,7 @@ async function bootstrapClientShellApp({
158
159
 
159
160
  const clientBootstrap = await bootClientModules({
160
161
  app,
162
+ pinia,
161
163
  router,
162
164
  surfaceRuntime,
163
165
  surfaceMode,
@@ -85,6 +85,7 @@ test("bootstrapClientShellApp boots modules, reinstalls fallback route, and moun
85
85
  const logs = [];
86
86
  const surfaceRuntime = createSurfaceRuntimeFixture();
87
87
  const plugin = { name: "vuetify-like-plugin" };
88
+ const pinia = { id: "pinia-instance" };
88
89
  const fallbackRoute = {
89
90
  name: "not-found",
90
91
  path: "/:pathMatch(.*)*",
@@ -140,10 +141,12 @@ test("bootstrapClientShellApp boots modules, reinstalls fallback route, and moun
140
141
  }
141
142
  },
142
143
  appPlugins: [plugin],
144
+ pinia,
143
145
  router,
144
146
  bootClientModules: async (context) => {
145
147
  calls.push("bootClientModules");
146
148
  assert.equal(context.app, app);
149
+ assert.equal(context.pinia, pinia);
147
150
  assert.equal(context.router, router);
148
151
  assert.equal(typeof context.logger.debug, "function");
149
152
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jskit-ai/kernel",
3
- "version": "0.1.37",
3
+ "version": "0.1.39",
4
4
  "type": "module",
5
5
  "dependencies": {
6
6
  "typebox": "^1.0.81"
@@ -19,6 +19,7 @@
19
19
  "./server/actions": "./server/actions/index.js",
20
20
  "./server/http": "./server/http/index.js",
21
21
  "./server/platform": "./server/platform/index.js",
22
+ "./server/registries": "./server/registries/index.js",
22
23
  "./server/runtime": "./server/runtime/index.js",
23
24
  "./server/runtime/errors": "./server/runtime/errors.js",
24
25
  "./server/runtime/requestUrl": "./server/runtime/requestUrl.js",
@@ -9,8 +9,21 @@ function registerBootstrapPayloadContributor(app, token, factory) {
9
9
 
10
10
  function resolveBootstrapPayloadContributors(scope) {
11
11
  return resolveTaggedEntries(scope, "jskit.runtime.bootstrap.payloadContributors")
12
- .map((entry) => normalizeContributorEntry(entry))
13
- .filter(Boolean);
12
+ .map((entry, index) => ({
13
+ contributor: normalizeContributorEntry(entry),
14
+ index
15
+ }))
16
+ .filter((entry) => Boolean(entry.contributor))
17
+ .sort((left, right) => {
18
+ const leftOrder = Number.isFinite(left.contributor?.order) ? Number(left.contributor.order) : 0;
19
+ const rightOrder = Number.isFinite(right.contributor?.order) ? Number(right.contributor.order) : 0;
20
+ if (leftOrder !== rightOrder) {
21
+ return leftOrder - rightOrder;
22
+ }
23
+
24
+ return left.index - right.index;
25
+ })
26
+ .map((entry) => entry.contributor);
14
27
  }
15
28
 
16
29
  async function resolveBootstrapPayload(scope, context = {}) {
@@ -87,6 +87,58 @@ test("resolveBootstrapPayload applies contributors in deterministic token order"
87
87
  });
88
88
  });
89
89
 
90
+ test("resolveBootstrapPayload honors explicit contributor order before token order", async () => {
91
+ const app = createContainer();
92
+ const calls = [];
93
+
94
+ registerBootstrapPayloadContributor(app, "test.bootstrap.alpha", () => ({
95
+ contributorId: "alpha",
96
+ order: 200,
97
+ contribute({ payload }) {
98
+ calls.push({
99
+ contributorId: "alpha",
100
+ payload
101
+ });
102
+ return {
103
+ alpha: true
104
+ };
105
+ }
106
+ }));
107
+
108
+ registerBootstrapPayloadContributor(app, "test.bootstrap.zeta", () => ({
109
+ contributorId: "zeta",
110
+ order: 100,
111
+ contribute({ payload }) {
112
+ calls.push({
113
+ contributorId: "zeta",
114
+ payload
115
+ });
116
+ return {
117
+ zeta: true
118
+ };
119
+ }
120
+ }));
121
+
122
+ const payload = await resolveBootstrapPayload(app);
123
+
124
+ assert.deepEqual(calls, [
125
+ {
126
+ contributorId: "zeta",
127
+ payload: {}
128
+ },
129
+ {
130
+ contributorId: "alpha",
131
+ payload: {
132
+ zeta: true
133
+ }
134
+ }
135
+ ]);
136
+ assert.deepEqual(payload, {
137
+ zeta: true,
138
+ alpha: true
139
+ });
140
+ });
141
+
90
142
  test("resolveBootstrapPayload ignores non-object contributions", async () => {
91
143
  const app = createContainer();
92
144
 
@@ -3,6 +3,15 @@ import { ROUTE_VISIBILITY_PUBLIC, ROUTE_VISIBILITY_USER } from "./policies.js";
3
3
 
4
4
  const ROUTE_VISIBILITY_LEVELS = Object.freeze([ROUTE_VISIBILITY_PUBLIC, ROUTE_VISIBILITY_USER]);
5
5
  const ROUTE_VISIBILITY_LEVEL_SET = new Set(ROUTE_VISIBILITY_LEVELS);
6
+ const ROUTE_VISIBILITY_WORKSPACE = "workspace";
7
+ const ROUTE_VISIBILITY_WORKSPACE_USER = "workspace_user";
8
+ const ROUTE_VISIBILITY_TOKENS = Object.freeze([
9
+ ROUTE_VISIBILITY_PUBLIC,
10
+ ROUTE_VISIBILITY_USER,
11
+ ROUTE_VISIBILITY_WORKSPACE,
12
+ ROUTE_VISIBILITY_WORKSPACE_USER
13
+ ]);
14
+ const ROUTE_VISIBILITY_TOKEN_SET = new Set(ROUTE_VISIBILITY_TOKENS);
6
15
 
7
16
  function normalizeRouteVisibilityToken(value, { fallback = ROUTE_VISIBILITY_PUBLIC } = {}) {
8
17
  const normalized = normalizeText(value).toLowerCase();
@@ -34,6 +43,22 @@ function normalizeRouteVisibility(value, { fallback = ROUTE_VISIBILITY_PUBLIC }
34
43
  return ROUTE_VISIBILITY_PUBLIC;
35
44
  }
36
45
 
46
+ function checkRouteVisibility(value, { context = "checkRouteVisibility" } = {}) {
47
+ const normalized = normalizeRouteVisibilityToken(value);
48
+ if (ROUTE_VISIBILITY_TOKEN_SET.has(normalized)) {
49
+ return normalized;
50
+ }
51
+
52
+ throw new TypeError(`${context} must be one of: ${ROUTE_VISIBILITY_TOKENS.join(", ")}.`);
53
+ }
54
+
55
+ function isWorkspaceRouteVisibility(value = "") {
56
+ const normalized = checkRouteVisibility(value, {
57
+ context: "isWorkspaceRouteVisibility visibility"
58
+ });
59
+ return normalized === ROUTE_VISIBILITY_WORKSPACE || normalized === ROUTE_VISIBILITY_WORKSPACE_USER;
60
+ }
61
+
37
62
  function normalizeVisibilityScopeKind(value) {
38
63
  const normalized = normalizeText(value).toLowerCase();
39
64
  return normalized || null;
@@ -53,4 +78,16 @@ function normalizeVisibilityContext(value = {}) {
53
78
  });
54
79
  }
55
80
 
56
- export { ROUTE_VISIBILITY_LEVELS, normalizeRouteVisibilityToken, normalizeRouteVisibility, normalizeVisibilityContext };
81
+ export {
82
+ ROUTE_VISIBILITY_LEVELS,
83
+ ROUTE_VISIBILITY_PUBLIC,
84
+ ROUTE_VISIBILITY_USER,
85
+ ROUTE_VISIBILITY_WORKSPACE,
86
+ ROUTE_VISIBILITY_WORKSPACE_USER,
87
+ ROUTE_VISIBILITY_TOKENS,
88
+ normalizeRouteVisibilityToken,
89
+ normalizeRouteVisibility,
90
+ checkRouteVisibility,
91
+ isWorkspaceRouteVisibility,
92
+ normalizeVisibilityContext
93
+ };
@@ -1,6 +1,12 @@
1
1
  import assert from "node:assert/strict";
2
2
  import test from "node:test";
3
- import { normalizeRouteVisibilityToken, normalizeRouteVisibility, normalizeVisibilityContext } from "./visibility.js";
3
+ import {
4
+ checkRouteVisibility,
5
+ isWorkspaceRouteVisibility,
6
+ normalizeRouteVisibilityToken,
7
+ normalizeRouteVisibility,
8
+ normalizeVisibilityContext
9
+ } from "./visibility.js";
4
10
 
5
11
  test("normalizeRouteVisibility keeps kernel core visibility contract", () => {
6
12
  assert.equal(normalizeRouteVisibility("PUBLIC"), "public");
@@ -18,6 +24,18 @@ test("normalizeRouteVisibilityToken normalizes visibility tokens for module-leve
18
24
  assert.equal(normalizeRouteVisibilityToken("", { fallback: "workspace" }), "workspace");
19
25
  });
20
26
 
27
+ test("checkRouteVisibility accepts workspace visibility tokens", () => {
28
+ assert.equal(checkRouteVisibility("workspace"), "workspace");
29
+ assert.equal(checkRouteVisibility("workspace_user"), "workspace_user");
30
+ assert.throws(() => checkRouteVisibility("invalid"), /must be one of/);
31
+ });
32
+
33
+ test("isWorkspaceRouteVisibility matches only workspace-scoped tokens", () => {
34
+ assert.equal(isWorkspaceRouteVisibility("workspace"), true);
35
+ assert.equal(isWorkspaceRouteVisibility("workspace_user"), true);
36
+ assert.equal(isWorkspaceRouteVisibility("public"), false);
37
+ });
38
+
21
39
  test("normalizeVisibilityContext normalizes mode and owner identifiers", () => {
22
40
  assert.deepEqual(normalizeVisibilityContext({ visibility: "user", userId: "7" }), {
23
41
  visibility: "user",
@@ -7,6 +7,76 @@ const API_DOCS_PATH = `${API_PREFIX}/docs`;
7
7
  const API_REALTIME_PATH = `${API_PREFIX}/realtime`;
8
8
  const VERSIONED_API_PATH_PATTERN = /^\/api\/v[0-9]+(?:$|\/)/;
9
9
 
10
+ function normalizeRouteTemplateParams(params = null) {
11
+ if (!params || typeof params !== "object" || Array.isArray(params)) {
12
+ return {};
13
+ }
14
+ return params;
15
+ }
16
+
17
+ function materializeRouteTemplate(routeTemplate = "/", { params = {}, strictParams = true, context = "materializeRouteTemplate" } = {}) {
18
+ const normalizedTemplate = normalizePathname(routeTemplate);
19
+ const normalizedParams = normalizeRouteTemplateParams(params);
20
+ const missingParams = new Set();
21
+ const outputPath = String(normalizedTemplate || "/").replace(/:([A-Za-z0-9_]+)/g, (_full, rawName) => {
22
+ const paramName = String(rawName || "").trim();
23
+ const rawValue = normalizedParams[paramName];
24
+ const paramValue = String(Array.isArray(rawValue) ? rawValue[0] : rawValue ?? "").trim();
25
+ if (!paramValue) {
26
+ missingParams.add(paramName);
27
+ return `:${paramName}`;
28
+ }
29
+ return encodeURIComponent(paramValue);
30
+ });
31
+
32
+ if (strictParams && missingParams.size > 0) {
33
+ throw new Error(`${context} missing required route params: ${[...missingParams].sort().join(", ")}.`);
34
+ }
35
+
36
+ return outputPath;
37
+ }
38
+
39
+ function resolveScopedRouteBase(routeBase = "/") {
40
+ const normalizedRouteBase = normalizePathname(routeBase);
41
+ const segments = normalizedRouteBase.split("/").filter(Boolean);
42
+ let lastDynamicIndex = -1;
43
+
44
+ for (let index = 0; index < segments.length; index += 1) {
45
+ const segment = segments[index];
46
+ if (segment.startsWith(":") && segment.length > 1) {
47
+ lastDynamicIndex = index;
48
+ }
49
+ }
50
+
51
+ if (lastDynamicIndex < 0) {
52
+ return "/";
53
+ }
54
+
55
+ return `/${segments.slice(0, lastDynamicIndex + 1).join("/")}`;
56
+ }
57
+
58
+ function resolveScopedApiBasePath({
59
+ routeBase = "/",
60
+ relativePath = "/",
61
+ params = {},
62
+ strictParams = true
63
+ } = {}) {
64
+ const scopedRouteBase = resolveScopedRouteBase(routeBase);
65
+ const materializedScopeBase = materializeRouteTemplate(scopedRouteBase, {
66
+ params,
67
+ strictParams,
68
+ context: "resolveScopedApiBasePath"
69
+ });
70
+ const basePath = materializedScopeBase === "/" ? API_BASE_PATH : `${API_BASE_PATH}${materializedScopeBase}`;
71
+ const normalizedRelativePath = normalizePathname(relativePath || "/");
72
+
73
+ if (normalizedRelativePath === "/") {
74
+ return basePath;
75
+ }
76
+
77
+ return `${basePath}${normalizedRelativePath}`;
78
+ }
79
+
10
80
  function isApiPath(pathname) {
11
81
  return matchesPathPrefix(pathname, API_BASE_PATH);
12
82
  }
@@ -74,11 +144,14 @@ export {
74
144
  API_PREFIX_SLASH,
75
145
  API_DOCS_PATH,
76
146
  API_REALTIME_PATH,
147
+ materializeRouteTemplate,
77
148
  normalizePathname,
78
149
  isApiPath,
79
150
  isVersionedApiPath,
80
151
  toVersionedApiPath,
81
152
  toVersionedApiPrefix,
82
153
  buildVersionedApiPath,
83
- isVersionedApiPrefixMatch
154
+ isVersionedApiPrefixMatch,
155
+ resolveScopedRouteBase,
156
+ resolveScopedApiBasePath
84
157
  };
@@ -0,0 +1,31 @@
1
+ import test from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { resolveScopedApiBasePath, resolveScopedRouteBase } from "./apiPaths.js";
4
+
5
+ test("resolveScopedRouteBase keeps the route prefix through the last dynamic segment", () => {
6
+ assert.equal(resolveScopedRouteBase("/"), "/");
7
+ assert.equal(resolveScopedRouteBase("/home"), "/");
8
+ assert.equal(resolveScopedRouteBase("/w/:workspaceSlug"), "/w/:workspaceSlug");
9
+ assert.equal(resolveScopedRouteBase("/w/:workspaceSlug/admin"), "/w/:workspaceSlug");
10
+ assert.equal(resolveScopedRouteBase("/org/:orgId/team/:teamId/admin"), "/org/:orgId/team/:teamId");
11
+ });
12
+
13
+ test("resolveScopedApiBasePath materializes scoped API paths from route templates", () => {
14
+ assert.equal(resolveScopedApiBasePath({ routeBase: "/home", relativePath: "/settings" }), "/api/settings");
15
+ assert.equal(
16
+ resolveScopedApiBasePath({
17
+ routeBase: "/w/:workspaceSlug/admin",
18
+ relativePath: "/members",
19
+ params: { workspaceSlug: "acme" }
20
+ }),
21
+ "/api/w/acme/members"
22
+ );
23
+ assert.throws(
24
+ () =>
25
+ resolveScopedApiBasePath({
26
+ routeBase: "/w/:workspaceSlug/admin",
27
+ relativePath: "/members"
28
+ }),
29
+ /missing required route params/
30
+ );
31
+ });
@@ -4,3 +4,7 @@ export {
4
4
  deriveSurfaceRouteBaseFromPagesRoot
5
5
  } from "./registry.js";
6
6
  export { createSurfacePathHelpers } from "./paths.js";
7
+ export {
8
+ resolveScopedRouteBase,
9
+ resolveScopedApiBasePath
10
+ } from "./apiPaths.js";