@jskit-ai/users-core 0.1.28 → 0.1.29

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.
@@ -1,7 +1,7 @@
1
1
  export default Object.freeze({
2
2
  packageVersion: 1,
3
3
  packageId: "@jskit-ai/users-core",
4
- version: "0.1.28",
4
+ version: "0.1.29",
5
5
  kind: "runtime",
6
6
  description: "Users/workspace domain runtime plus HTTP routes for workspace, account, and console features.",
7
7
  dependsOn: [
@@ -196,10 +196,10 @@ export default Object.freeze({
196
196
  mutations: {
197
197
  dependencies: {
198
198
  runtime: {
199
- "@jskit-ai/auth-core": "0.1.18",
200
- "@jskit-ai/database-runtime": "0.1.19",
201
- "@jskit-ai/http-runtime": "0.1.18",
202
- "@jskit-ai/kernel": "0.1.19",
199
+ "@jskit-ai/auth-core": "0.1.19",
200
+ "@jskit-ai/database-runtime": "0.1.20",
201
+ "@jskit-ai/http-runtime": "0.1.19",
202
+ "@jskit-ai/kernel": "0.1.20",
203
203
  "@fastify/multipart": "^9.4.0",
204
204
  "@fastify/type-provider-typebox": "^6.1.0",
205
205
  "typebox": "^1.0.81"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jskit-ai/users-core",
3
- "version": "0.1.28",
3
+ "version": "0.1.29",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "test": "node --test"
@@ -24,10 +24,10 @@
24
24
  "./shared/resources/consoleSettingsFields": "./src/shared/resources/consoleSettingsFields.js"
25
25
  },
26
26
  "dependencies": {
27
- "@jskit-ai/auth-core": "0.1.18",
28
- "@jskit-ai/database-runtime": "0.1.19",
29
- "@jskit-ai/http-runtime": "0.1.18",
30
- "@jskit-ai/kernel": "0.1.19",
27
+ "@jskit-ai/auth-core": "0.1.19",
28
+ "@jskit-ai/database-runtime": "0.1.20",
29
+ "@jskit-ai/http-runtime": "0.1.19",
30
+ "@jskit-ai/kernel": "0.1.20",
31
31
  "@fastify/multipart": "^9.4.0",
32
32
  "@fastify/type-provider-typebox": "^6.1.0",
33
33
  "typebox": "^1.0.81"
@@ -3,7 +3,7 @@ import {
3
3
  requireServiceMethod
4
4
  } from "@jskit-ai/kernel/shared/actions/actionContributorHelpers";
5
5
  import {
6
- normalizeScopedRouteVisibility,
6
+ checkRouteVisibility,
7
7
  USERS_ROUTE_VISIBILITY_PUBLIC,
8
8
  USERS_ROUTE_VISIBILITY_WORKSPACE,
9
9
  USERS_ROUTE_VISIBILITY_WORKSPACE_USER
@@ -41,9 +41,11 @@ function createWorkspaceActionContextContributor({ workspaceService } = {}) {
41
41
 
42
42
  const actionName = String(actionId || "").trim();
43
43
  const hasLegacyWorkspaceActionId = WORKSPACE_CONTEXT_ACTION_IDS.includes(actionName);
44
- const routeVisibility = normalizeScopedRouteVisibility(request?.routeOptions?.config?.visibility, {
45
- fallback: USERS_ROUTE_VISIBILITY_PUBLIC
46
- });
44
+ const routeVisibilityInput =
45
+ request && request.routeOptions && request.routeOptions.config
46
+ ? request.routeOptions.config.visibility
47
+ : USERS_ROUTE_VISIBILITY_PUBLIC;
48
+ const routeVisibility = checkRouteVisibility(routeVisibilityInput);
47
49
  const hasWorkspaceRouteVisibility = WORKSPACE_VISIBILITY_ACTION_CONTEXT_SET.has(routeVisibility);
48
50
  if (!hasLegacyWorkspaceActionId && !hasWorkspaceRouteVisibility) {
49
51
  return {};
@@ -53,13 +53,16 @@ function requireSynchronizedProfile(profile) {
53
53
  throw new Error("Profile synchronization failed.");
54
54
  }
55
55
 
56
- function createService({ userProfilesRepository, workspaceProvisioningService = null } = {}) {
56
+ function createService({ userProfilesRepository, workspaceProvisioningService = null, userSettingsRepository = null } = {}) {
57
57
  if (!userProfilesRepository || typeof userProfilesRepository.findByIdentity !== "function") {
58
58
  throw new Error("authProfileSyncService requires userProfilesRepository.findByIdentity().");
59
59
  }
60
60
  if (typeof userProfilesRepository.upsert !== "function") {
61
61
  throw new Error("authProfileSyncService requires userProfilesRepository.upsert().");
62
62
  }
63
+ if (!userSettingsRepository || typeof userSettingsRepository.ensureForUserId !== "function") {
64
+ throw new Error("authProfileSyncService requires userSettingsRepository.ensureForUserId().");
65
+ }
63
66
 
64
67
  async function findByIdentity(identityLike, options = {}) {
65
68
  const normalized = buildNormalizedIdentityKey(identityLike);
@@ -93,11 +96,14 @@ function createService({ userProfilesRepository, workspaceProvisioningService =
93
96
  const operationOptions = trx ? { ...options, trx } : options;
94
97
  const existing = await findByIdentity(normalized, operationOptions);
95
98
  if (!profileNeedsUpdate(existing, normalized)) {
96
- return requireSynchronizedProfile(existing);
99
+ const synchronizedProfile = requireSynchronizedProfile(existing);
100
+ await userSettingsRepository.ensureForUserId(synchronizedProfile.id, operationOptions);
101
+ return synchronizedProfile;
97
102
  }
98
103
 
99
104
  const upserted = await upsertByIdentity(normalized, operationOptions);
100
105
  const synchronizedProfile = requireSynchronizedProfile(upserted);
106
+ await userSettingsRepository.ensureForUserId(synchronizedProfile.id, operationOptions);
101
107
  if (
102
108
  !existing &&
103
109
  workspaceProvisioningService &&
@@ -30,6 +30,7 @@ function registerWorkspaceCore(app) {
30
30
  app.singleton("users.profile.sync.service", (scope) => {
31
31
  return createAuthProfileSyncService({
32
32
  userProfilesRepository: scope.make("userProfilesRepository"),
33
+ userSettingsRepository: scope.make("userSettingsRepository"),
33
34
  workspaceProvisioningService: scope.make("users.workspace.service")
34
35
  });
35
36
  });
@@ -1,4 +1,4 @@
1
- import { normalizeRouteVisibilityToken } from "@jskit-ai/kernel/shared/support/visibility";
1
+ import { normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
2
2
 
3
3
  const USERS_ROUTE_VISIBILITY_PUBLIC = "public";
4
4
  const USERS_ROUTE_VISIBILITY_USER = "user";
@@ -13,23 +13,20 @@ const USERS_ROUTE_VISIBILITY_LEVELS = Object.freeze([
13
13
  ]);
14
14
  const USERS_ROUTE_VISIBILITY_LEVEL_SET = new Set(USERS_ROUTE_VISIBILITY_LEVELS);
15
15
 
16
- function normalizeScopedRouteVisibility(value, { fallback = USERS_ROUTE_VISIBILITY_PUBLIC } = {}) {
17
- const normalized = normalizeRouteVisibilityToken(value, { fallback: USERS_ROUTE_VISIBILITY_PUBLIC });
16
+ function checkRouteVisibility(value, { context = "checkRouteVisibility" } = {}) {
17
+ const normalized = normalizeText(value).toLowerCase();
18
18
  if (USERS_ROUTE_VISIBILITY_LEVEL_SET.has(normalized)) {
19
19
  return normalized;
20
20
  }
21
21
 
22
- const normalizedFallback = normalizeRouteVisibilityToken(fallback, { fallback: USERS_ROUTE_VISIBILITY_PUBLIC });
23
- if (USERS_ROUTE_VISIBILITY_LEVEL_SET.has(normalizedFallback)) {
24
- return normalizedFallback;
25
- }
26
-
27
- return USERS_ROUTE_VISIBILITY_PUBLIC;
22
+ throw new TypeError(
23
+ `${context} must be one of: ${USERS_ROUTE_VISIBILITY_LEVELS.join(", ")}.`
24
+ );
28
25
  }
29
26
 
30
27
  function isWorkspaceVisibility(visibility = "") {
31
- const normalized = normalizeScopedRouteVisibility(visibility, {
32
- fallback: USERS_ROUTE_VISIBILITY_PUBLIC
28
+ const normalized = checkRouteVisibility(visibility, {
29
+ context: "isWorkspaceVisibility visibility"
33
30
  });
34
31
  return normalized === USERS_ROUTE_VISIBILITY_WORKSPACE || normalized === USERS_ROUTE_VISIBILITY_WORKSPACE_USER;
35
32
  }
@@ -40,6 +37,6 @@ export {
40
37
  USERS_ROUTE_VISIBILITY_WORKSPACE,
41
38
  USERS_ROUTE_VISIBILITY_WORKSPACE_USER,
42
39
  USERS_ROUTE_VISIBILITY_LEVELS,
43
- normalizeScopedRouteVisibility,
40
+ checkRouteVisibility,
44
41
  isWorkspaceVisibility
45
42
  };
@@ -27,6 +27,12 @@ test("authProfileSyncService.syncIdentityProfile uses shared transaction for pro
27
27
  return work(transaction);
28
28
  }
29
29
  },
30
+ userSettingsRepository: {
31
+ async ensureForUserId(userId, options = {}) {
32
+ calls.push({ step: "ensureUserSettings", userId: Number(userId), trx: options.trx || null });
33
+ return { userId: Number(userId) };
34
+ }
35
+ },
30
36
  workspaceProvisioningService: {
31
37
  async provisionWorkspaceForNewUser(_profile, options = {}) {
32
38
  calls.push({ step: "provision", trx: options.trx || null });
@@ -45,10 +51,12 @@ test("authProfileSyncService.syncIdentityProfile uses shared transaction for pro
45
51
  assert.equal(calls[0].step, "withTransaction");
46
52
  assert.equal(calls[1].step, "find");
47
53
  assert.equal(calls[2].step, "upsert");
48
- assert.equal(calls[3].step, "provision");
54
+ assert.equal(calls[3].step, "ensureUserSettings");
55
+ assert.equal(calls[4].step, "provision");
49
56
  assert.equal(calls[1].trx, transaction);
50
57
  assert.equal(calls[2].trx, transaction);
51
58
  assert.equal(calls[3].trx, transaction);
59
+ assert.equal(calls[4].trx, transaction);
52
60
  });
53
61
 
54
62
  test("authProfileSyncService.syncIdentityProfile skips write path when profile is unchanged", async () => {
@@ -74,6 +82,11 @@ test("authProfileSyncService.syncIdentityProfile skips write path when profile i
74
82
  return work({ trxId: "tx-2" });
75
83
  }
76
84
  },
85
+ userSettingsRepository: {
86
+ async ensureForUserId() {
87
+ return { userId: 7 };
88
+ }
89
+ },
77
90
  workspaceProvisioningService: {
78
91
  async provisionWorkspaceForNewUser() {
79
92
  provisionCalls += 1;
@@ -104,6 +117,11 @@ test("authProfileSyncService.findByIdentity normalizes provider identity input",
104
117
  async upsert() {
105
118
  return null;
106
119
  }
120
+ },
121
+ userSettingsRepository: {
122
+ async ensureForUserId() {
123
+ return { userId: 1 };
124
+ }
107
125
  }
108
126
  });
109
127
 
@@ -2,16 +2,21 @@ import assert from "node:assert/strict";
2
2
  import test from "node:test";
3
3
  import {
4
4
  isWorkspaceVisibility,
5
- normalizeScopedRouteVisibility
5
+ checkRouteVisibility
6
6
  } from "../src/shared/support/usersVisibility.js";
7
7
 
8
- test("normalizeScopedRouteVisibility normalizes users visibility levels", () => {
9
- assert.equal(normalizeScopedRouteVisibility("WORKSPACE"), "workspace");
10
- assert.equal(normalizeScopedRouteVisibility("workspace_user"), "workspace_user");
11
- assert.equal(normalizeScopedRouteVisibility("user"), "user");
12
- assert.equal(normalizeScopedRouteVisibility(""), "public");
13
- assert.equal(normalizeScopedRouteVisibility("unknown"), "public");
14
- assert.equal(normalizeScopedRouteVisibility("unknown", { fallback: "workspace" }), "workspace");
8
+ test("checkRouteVisibility normalizes valid users visibility levels and throws on invalid input", () => {
9
+ assert.equal(checkRouteVisibility("WORKSPACE"), "workspace");
10
+ assert.equal(checkRouteVisibility("workspace_user"), "workspace_user");
11
+ assert.equal(checkRouteVisibility("user"), "user");
12
+ assert.throws(
13
+ () => checkRouteVisibility(""),
14
+ /must be one of/
15
+ );
16
+ assert.throws(
17
+ () => checkRouteVisibility("unknown"),
18
+ /must be one of/
19
+ );
15
20
  });
16
21
 
17
22
  test("isWorkspaceVisibility recognizes workspace-only visibility levels", () => {