@jskit-ai/users-core 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 (148) hide show
  1. package/package.descriptor.mjs +464 -0
  2. package/package.json +35 -0
  3. package/src/server/UsersCoreServiceProvider.js +74 -0
  4. package/src/server/accountNotifications/accountNotificationsActions.js +39 -0
  5. package/src/server/accountNotifications/accountNotificationsService.js +41 -0
  6. package/src/server/accountNotifications/bootAccountNotificationsRoutes.js +41 -0
  7. package/src/server/accountNotifications/registerAccountNotifications.js +39 -0
  8. package/src/server/accountPreferences/accountPreferencesActions.js +39 -0
  9. package/src/server/accountPreferences/accountPreferencesService.js +41 -0
  10. package/src/server/accountPreferences/bootAccountPreferencesRoutes.js +41 -0
  11. package/src/server/accountPreferences/registerAccountPreferences.js +39 -0
  12. package/src/server/accountProfile/accountProfileActions.js +137 -0
  13. package/src/server/accountProfile/accountProfileService.js +124 -0
  14. package/src/server/accountProfile/avatarService.js +141 -0
  15. package/src/server/accountProfile/avatarStorageService.js +132 -0
  16. package/src/server/accountProfile/bootAccountProfileRoutes.js +166 -0
  17. package/src/server/accountProfile/registerAccountProfile.js +62 -0
  18. package/src/server/accountProfile/registerAvatarMultipartSupport.js +43 -0
  19. package/src/server/accountSecurity/accountSecurityActions.js +144 -0
  20. package/src/server/accountSecurity/accountSecurityService.js +103 -0
  21. package/src/server/accountSecurity/bootAccountSecurityRoutes.js +183 -0
  22. package/src/server/accountSecurity/registerAccountSecurity.js +31 -0
  23. package/src/server/common/README.md +21 -0
  24. package/src/server/common/contributors/README.md +11 -0
  25. package/src/server/common/contributors/workspaceActionContextContributor.js +79 -0
  26. package/src/server/common/contributors/workspaceAuthPolicyContextResolver.js +34 -0
  27. package/src/server/common/contributors/workspaceRouteVisibilityResolver.js +79 -0
  28. package/src/server/common/diTokens.js +21 -0
  29. package/src/server/common/formatters/README.md +11 -0
  30. package/src/server/common/formatters/accountAvatarFormatter.js +42 -0
  31. package/src/server/common/formatters/accountSecurityStatusFormatter.js +71 -0
  32. package/src/server/common/formatters/accountSettingsResponseFormatter.js +62 -0
  33. package/src/server/common/formatters/workspaceFormatter.js +46 -0
  34. package/src/server/common/registerCommonRepositories.js +45 -0
  35. package/src/server/common/registerSharedApi.js +9 -0
  36. package/src/server/common/repositories/README.md +24 -0
  37. package/src/server/common/repositories/repositoryUtils.js +50 -0
  38. package/src/server/common/repositories/userProfilesRepository.js +251 -0
  39. package/src/server/common/repositories/userSettingsRepository.js +179 -0
  40. package/src/server/common/repositories/workspaceInvitesRepository.js +172 -0
  41. package/src/server/common/repositories/workspaceMembershipsRepository.js +157 -0
  42. package/src/server/common/repositories/workspacesRepository.js +183 -0
  43. package/src/server/common/routes/README.md +11 -0
  44. package/src/server/common/services/README.md +12 -0
  45. package/src/server/common/services/accountContextService.js +31 -0
  46. package/src/server/common/services/authProfileSyncService.js +128 -0
  47. package/src/server/common/services/workspaceContextService.js +270 -0
  48. package/src/server/common/support/deepFreeze.js +17 -0
  49. package/src/server/common/support/realtimeServiceEvents.js +94 -0
  50. package/src/server/common/support/resolveActionUser.js +11 -0
  51. package/src/server/common/support/workspaceRoutePaths.js +17 -0
  52. package/src/server/common/validators/README.md +11 -0
  53. package/src/server/common/validators/authenticatedUserValidator.js +42 -0
  54. package/src/server/common/validators/routeParamsValidator.js +62 -0
  55. package/src/server/consoleSettings/bootConsoleSettingsRoutes.js +64 -0
  56. package/src/server/consoleSettings/consoleService.js +36 -0
  57. package/src/server/consoleSettings/consoleSettingsActions.js +55 -0
  58. package/src/server/consoleSettings/consoleSettingsRepository.js +111 -0
  59. package/src/server/consoleSettings/consoleSettingsService.js +40 -0
  60. package/src/server/consoleSettings/registerConsoleSettings.js +57 -0
  61. package/src/server/registerWorkspaceBootstrap.js +36 -0
  62. package/src/server/registerWorkspaceCore.js +95 -0
  63. package/src/server/support/resolveWorkspace.js +16 -0
  64. package/src/server/support/workspaceActionSurfaces.js +135 -0
  65. package/src/server/support/workspaceInvitationsPolicy.js +45 -0
  66. package/src/server/support/workspaceRouteInput.js +22 -0
  67. package/src/server/workspaceBootstrapContributor.js +401 -0
  68. package/src/server/workspaceDirectory/bootWorkspaceDirectoryRoutes.js +73 -0
  69. package/src/server/workspaceDirectory/registerWorkspaceDirectory.js +19 -0
  70. package/src/server/workspaceDirectory/workspaceDirectoryActions.js +65 -0
  71. package/src/server/workspaceMembers/bootWorkspaceMembers.js +238 -0
  72. package/src/server/workspaceMembers/registerWorkspaceMembers.js +112 -0
  73. package/src/server/workspaceMembers/workspaceMembersActions.js +186 -0
  74. package/src/server/workspaceMembers/workspaceMembersService.js +210 -0
  75. package/src/server/workspacePendingInvitations/bootWorkspacePendingInvitations.js +63 -0
  76. package/src/server/workspacePendingInvitations/registerWorkspacePendingInvitations.js +128 -0
  77. package/src/server/workspacePendingInvitations/workspacePendingInvitationsActions.js +74 -0
  78. package/src/server/workspacePendingInvitations/workspacePendingInvitationsService.js +137 -0
  79. package/src/server/workspaceSettings/bootWorkspaceSettings.js +77 -0
  80. package/src/server/workspaceSettings/registerWorkspaceSettings.js +67 -0
  81. package/src/server/workspaceSettings/workspaceSettingsActions.js +72 -0
  82. package/src/server/workspaceSettings/workspaceSettingsRepository.js +135 -0
  83. package/src/server/workspaceSettings/workspaceSettingsService.js +65 -0
  84. package/src/shared/events/usersEvents.js +19 -0
  85. package/src/shared/index.js +91 -0
  86. package/src/shared/operationMessages.js +16 -0
  87. package/src/shared/resources/consoleSettingsFields.js +55 -0
  88. package/src/shared/resources/consoleSettingsResource.js +139 -0
  89. package/src/shared/resources/resolveGlobalArrayRegistry.js +6 -0
  90. package/src/shared/resources/userProfileResource.js +148 -0
  91. package/src/shared/resources/userSettingsFields.js +71 -0
  92. package/src/shared/resources/userSettingsResource.js +416 -0
  93. package/src/shared/resources/workspaceMembersResource.js +352 -0
  94. package/src/shared/resources/workspacePendingInvitationsResource.js +87 -0
  95. package/src/shared/resources/workspaceResource.js +149 -0
  96. package/src/shared/resources/workspaceSettingsFields.js +60 -0
  97. package/src/shared/resources/workspaceSettingsResource.js +178 -0
  98. package/src/shared/roles.js +136 -0
  99. package/src/shared/settings.js +31 -0
  100. package/src/shared/support/usersApiPaths.js +34 -0
  101. package/src/shared/support/usersVisibility.js +45 -0
  102. package/src/shared/support/workspacePathModel.js +145 -0
  103. package/src/shared/tenancyMode.js +35 -0
  104. package/src/shared/tenancyProfile.js +73 -0
  105. package/templates/config/workspaceRoles.js +30 -0
  106. package/templates/migrations/users_core_console_owner.cjs +39 -0
  107. package/templates/migrations/users_core_initial.cjs +118 -0
  108. package/templates/migrations/users_core_profile_username.cjs +98 -0
  109. package/templates/packages/main/src/shared/resources/consoleSettingsFields.js +11 -0
  110. package/templates/packages/main/src/shared/resources/userSettingsFields.js +138 -0
  111. package/templates/packages/main/src/shared/resources/workspaceSettingsFields.js +105 -0
  112. package/test/authProfileSyncService.test.js +119 -0
  113. package/test/avatarService.test.js +114 -0
  114. package/test/avatarStorageService.test.js +61 -0
  115. package/test/consoleService.test.js +57 -0
  116. package/test/consoleSettingsService.test.js +86 -0
  117. package/test/exportsContract.test.js +38 -0
  118. package/test/registerAvatarMultipartSupport.test.js +64 -0
  119. package/test/registerServiceRealtimeEvents.test.js +160 -0
  120. package/test/registerWorkspaceDirectory.test.js +26 -0
  121. package/test/registerWorkspaceSettings.test.js +44 -0
  122. package/test/resourcesCanonical.test.js +90 -0
  123. package/test/roles.test.js +74 -0
  124. package/test/settingsFieldRegistriesSingleton.test.js +24 -0
  125. package/test/tenancyProfile.test.js +67 -0
  126. package/test/userSettingsResource.test.js +31 -0
  127. package/test/usersApiPaths.test.js +31 -0
  128. package/test/usersRouteRequestInputValidator.test.js +556 -0
  129. package/test/usersRouteResources.test.js +113 -0
  130. package/test/usersRouteValidators.test.js +49 -0
  131. package/test/usersVisibility.test.js +22 -0
  132. package/test/workspaceActionContextContributor.test.js +251 -0
  133. package/test/workspaceActionSurfaces.test.js +105 -0
  134. package/test/workspaceAuthPolicyContextResolver.test.js +119 -0
  135. package/test/workspaceBootstrapContributor.test.js +466 -0
  136. package/test/workspaceInvitationsPolicy.test.js +71 -0
  137. package/test/workspaceInvitesRepository.test.js +111 -0
  138. package/test/workspaceMembersService.test.js +400 -0
  139. package/test/workspacePathModel.test.js +93 -0
  140. package/test/workspacePendingInvitationsResource.test.js +38 -0
  141. package/test/workspacePendingInvitationsService.test.js +151 -0
  142. package/test/workspaceRouteVisibilityResolver.test.js +83 -0
  143. package/test/workspaceService.test.js +480 -0
  144. package/test/workspaceSettingsActions.test.js +42 -0
  145. package/test/workspaceSettingsRepository.test.js +156 -0
  146. package/test/workspaceSettingsResource.test.js +156 -0
  147. package/test/workspaceSettingsService.test.js +120 -0
  148. package/test-support/registerDefaultSettingsFields.js +3 -0
@@ -0,0 +1,111 @@
1
+ import {
2
+ nowDb,
3
+ toIsoString
4
+ } from "../common/repositories/repositoryUtils.js";
5
+ import { parsePositiveInteger } from "@jskit-ai/kernel/server/runtime";
6
+ import { normalizeObjectInput } from "@jskit-ai/kernel/shared/validators/inputNormalization";
7
+ import { consoleSettingsFields } from "../../shared/resources/consoleSettingsFields.js";
8
+
9
+ function mapSettings(row = {}) {
10
+ const settings = {};
11
+ for (const field of consoleSettingsFields) {
12
+ const rawValue = Object.hasOwn(row, field.dbColumn)
13
+ ? row[field.dbColumn]
14
+ : field.resolveDefault({
15
+ settings: row
16
+ });
17
+ settings[field.key] = field.normalizeOutput(rawValue, {
18
+ settings: row
19
+ });
20
+ }
21
+ return settings;
22
+ }
23
+
24
+ function mapSingletonRow(row) {
25
+ if (!row) {
26
+ throw new Error("console_settings singleton row is missing.");
27
+ }
28
+
29
+ const ownerUserId = parsePositiveInteger(row.owner_user_id);
30
+ return {
31
+ id: Number(row.id),
32
+ ownerUserId: ownerUserId || null,
33
+ settings: mapSettings(row),
34
+ createdAt: toIsoString(row.created_at),
35
+ updatedAt: toIsoString(row.updated_at)
36
+ };
37
+ }
38
+
39
+ function createRepository(knex) {
40
+ if (typeof knex !== "function") {
41
+ throw new TypeError("consoleSettingsRepository requires knex.");
42
+ }
43
+
44
+ async function readSingleton(client) {
45
+ return client("console_settings").where({ id: 1 }).first();
46
+ }
47
+
48
+ async function getSingleton(options = {}) {
49
+ const client = options?.trx || knex;
50
+ return mapSingletonRow(await readSingleton(client));
51
+ }
52
+
53
+ async function ensureOwnerUserId(userId, options = {}) {
54
+ const client = options?.trx || knex;
55
+ const candidateOwnerUserId = parsePositiveInteger(userId);
56
+ if (!candidateOwnerUserId) {
57
+ throw new TypeError("consoleSettingsRepository.ensureOwnerUserId requires a positive user id.");
58
+ }
59
+
60
+ const current = mapSingletonRow(await readSingleton(client));
61
+ if (current.ownerUserId) {
62
+ return current.ownerUserId;
63
+ }
64
+
65
+ await client("console_settings")
66
+ .where({ id: 1 })
67
+ .whereNull("owner_user_id")
68
+ .update({
69
+ owner_user_id: candidateOwnerUserId,
70
+ updated_at: nowDb()
71
+ });
72
+
73
+ const reloaded = mapSingletonRow(await readSingleton(client));
74
+ if (!reloaded.ownerUserId) {
75
+ throw new Error("console_settings owner_user_id could not be resolved.");
76
+ }
77
+
78
+ return reloaded.ownerUserId;
79
+ }
80
+
81
+ async function updateSingleton(patch, options = {}) {
82
+ const client = options?.trx || knex;
83
+ const source = normalizeObjectInput(patch);
84
+ const dbPatch = {
85
+ updated_at: nowDb()
86
+ };
87
+
88
+ for (const field of consoleSettingsFields) {
89
+ if (!Object.hasOwn(source, field.key)) {
90
+ continue;
91
+ }
92
+ dbPatch[field.dbColumn] = field.normalizeInput(source[field.key], {
93
+ payload: source
94
+ });
95
+ }
96
+
97
+ await client("console_settings")
98
+ .where({ id: 1 })
99
+ .update(dbPatch);
100
+
101
+ return getSingleton({ trx: client });
102
+ }
103
+
104
+ return Object.freeze({
105
+ getSingleton,
106
+ ensureOwnerUserId,
107
+ updateSingleton
108
+ });
109
+ }
110
+
111
+ export { createRepository };
@@ -0,0 +1,40 @@
1
+ function buildSettingsResponse(record = {}) {
2
+ return {
3
+ settings: {
4
+ ...(record?.settings && typeof record.settings === "object" ? record.settings : {})
5
+ }
6
+ };
7
+ }
8
+
9
+ function createService({ consoleSettingsRepository, consoleService } = {}) {
10
+ if (!consoleSettingsRepository || typeof consoleSettingsRepository.getSingleton !== "function") {
11
+ throw new Error("consoleSettingsService requires consoleSettingsRepository.getSingleton().");
12
+ }
13
+ if (!consoleSettingsRepository || typeof consoleSettingsRepository.updateSingleton !== "function") {
14
+ throw new Error("consoleSettingsService requires consoleSettingsRepository.updateSingleton().");
15
+ }
16
+ if (!consoleService || typeof consoleService.requireConsoleOwner !== "function") {
17
+ throw new Error("consoleSettingsService requires consoleService.requireConsoleOwner().");
18
+ }
19
+
20
+ async function getSettings(options = {}) {
21
+ await consoleService.requireConsoleOwner(options?.context, options);
22
+ const settings = await consoleSettingsRepository.getSingleton();
23
+
24
+ return buildSettingsResponse(settings);
25
+ }
26
+
27
+ async function updateSettings(input = {}, options = {}) {
28
+ await consoleService.requireConsoleOwner(options?.context, options);
29
+ const settings = await consoleSettingsRepository.updateSingleton(input);
30
+
31
+ return buildSettingsResponse(settings);
32
+ }
33
+
34
+ return Object.freeze({
35
+ getSettings,
36
+ updateSettings
37
+ });
38
+ }
39
+
40
+ export { createService };
@@ -0,0 +1,57 @@
1
+ import { withActionDefaults } from "@jskit-ai/kernel/shared/actions";
2
+ import { CONSOLE_SETTINGS_CHANGED_EVENT } from "../../shared/events/usersEvents.js";
3
+ import { createService as createConsoleSettingsService } from "./consoleSettingsService.js";
4
+ import { createService as createConsoleService } from "./consoleService.js";
5
+ import { consoleSettingsActions } from "./consoleSettingsActions.js";
6
+
7
+ function registerConsoleSettings(app) {
8
+ if (!app || typeof app.singleton !== "function" || typeof app.service !== "function" || typeof app.actions !== "function") {
9
+ throw new Error("registerConsoleSettings requires application singleton()/service()/actions().");
10
+ }
11
+
12
+ const hasConsoleService = typeof app.has === "function" ? app.has("consoleService") : false;
13
+ if (!hasConsoleService) {
14
+ app.singleton("consoleService", (scope) =>
15
+ createConsoleService({
16
+ consoleSettingsRepository: scope.make("consoleSettingsRepository")
17
+ })
18
+ );
19
+ }
20
+
21
+ app.service(
22
+ "users.console.settings.service",
23
+ (scope) =>
24
+ createConsoleSettingsService({
25
+ consoleSettingsRepository: scope.make("consoleSettingsRepository"),
26
+ consoleService: scope.make("consoleService")
27
+ }),
28
+ {
29
+ events: Object.freeze({
30
+ updateSettings: Object.freeze([
31
+ Object.freeze({
32
+ type: "entity.changed",
33
+ source: "console",
34
+ entity: "settings",
35
+ operation: "updated",
36
+ entityId: 1,
37
+ realtime: Object.freeze({
38
+ event: CONSOLE_SETTINGS_CHANGED_EVENT,
39
+ audience: "all_users"
40
+ })
41
+ })
42
+ ])
43
+ })
44
+ }
45
+ );
46
+
47
+ app.actions(
48
+ withActionDefaults(consoleSettingsActions, {
49
+ domain: "console",
50
+ dependencies: {
51
+ consoleSettingsService: "users.console.settings.service"
52
+ }
53
+ })
54
+ );
55
+ }
56
+
57
+ export { registerConsoleSettings };
@@ -0,0 +1,36 @@
1
+ import { registerBootstrapPayloadContributor } from "@jskit-ai/kernel/server/runtime";
2
+ import { resolveAppConfig } from "@jskit-ai/kernel/server/support";
3
+ import {
4
+ USERS_WORKSPACE_INVITATIONS_ENABLED_TOKEN,
5
+ USERS_TENANCY_PROFILE_TOKEN,
6
+ USERS_WORKSPACE_PENDING_INVITATIONS_SERVICE_TOKEN
7
+ } from "./common/diTokens.js";
8
+ import { createWorkspaceBootstrapContributor } from "./workspaceBootstrapContributor.js";
9
+
10
+ const USERS_WORKSPACE_BOOTSTRAP_PAYLOAD_CONTRIBUTOR_TOKEN = "users.core.workspace.bootstrap.payloadContributor";
11
+
12
+ function registerWorkspaceBootstrap(app) {
13
+ if (!app || typeof app.singleton !== "function") {
14
+ throw new Error("registerWorkspaceBootstrap requires application singleton().");
15
+ }
16
+
17
+ registerBootstrapPayloadContributor(app, USERS_WORKSPACE_BOOTSTRAP_PAYLOAD_CONTRIBUTOR_TOKEN, (scope) => {
18
+ const workspaceInvitationsEnabled = scope.make(USERS_WORKSPACE_INVITATIONS_ENABLED_TOKEN);
19
+
20
+ return createWorkspaceBootstrapContributor({
21
+ workspaceService: scope.make("users.workspace.service"),
22
+ workspacePendingInvitationsService: workspaceInvitationsEnabled
23
+ ? scope.make(USERS_WORKSPACE_PENDING_INVITATIONS_SERVICE_TOKEN)
24
+ : null,
25
+ workspaceInvitationsEnabled,
26
+ userProfilesRepository: scope.make("userProfilesRepository"),
27
+ userSettingsRepository: scope.make("userSettingsRepository"),
28
+ appConfig: resolveAppConfig(scope),
29
+ tenancyProfile: scope.make(USERS_TENANCY_PROFILE_TOKEN),
30
+ authService: scope.make("authService"),
31
+ consoleService: scope.has("consoleService") ? scope.make("consoleService") : null
32
+ });
33
+ });
34
+ }
35
+
36
+ export { registerWorkspaceBootstrap };
@@ -0,0 +1,95 @@
1
+ import { AUTH_POLICY_CONTEXT_RESOLVER_TOKEN } from "@jskit-ai/auth-core/server/lib/tokens";
2
+ import {
3
+ registerActionContextContributor
4
+ } from "@jskit-ai/kernel/server/actions";
5
+ import { registerRouteVisibilityResolver } from "@jskit-ai/kernel/server/http";
6
+ import { resolveAppConfig } from "@jskit-ai/kernel/server/support";
7
+ import { TENANCY_MODE_WORKSPACE, resolveTenancyProfile } from "../shared/tenancyProfile.js";
8
+ import { createService as createWorkspaceService } from "./common/services/workspaceContextService.js";
9
+ import { createService as createAuthProfileSyncService } from "./common/services/authProfileSyncService.js";
10
+ import { createWorkspaceActionContextContributor } from "./common/contributors/workspaceActionContextContributor.js";
11
+ import { createWorkspaceRouteVisibilityResolver } from "./common/contributors/workspaceRouteVisibilityResolver.js";
12
+ import { createWorkspaceAuthPolicyContextResolver } from "./common/contributors/workspaceAuthPolicyContextResolver.js";
13
+ import { resolveWorkspaceInvitationsPolicy } from "./support/workspaceInvitationsPolicy.js";
14
+ import {
15
+ USERS_PROFILE_SYNC_SERVICE_TOKEN,
16
+ USERS_TENANCY_PROFILE_TOKEN,
17
+ USERS_WORKSPACE_ENABLED_TOKEN,
18
+ USERS_WORKSPACE_INVITATIONS_ENABLED_TOKEN,
19
+ USERS_WORKSPACE_SELF_CREATE_ENABLED_TOKEN,
20
+ USERS_WORKSPACE_TENANCY_ENABLED_TOKEN
21
+ } from "./common/diTokens.js";
22
+
23
+ const USERS_WORKSPACE_CONTEXT_CONTRIBUTOR_TOKEN = "users.core.workspace.actionContextContributor";
24
+ const USERS_WORKSPACE_VISIBILITY_RESOLVER_TOKEN = "users.core.workspace.routeVisibilityResolver";
25
+
26
+ function registerWorkspaceCore(app) {
27
+ if (!app || typeof app.singleton !== "function") {
28
+ throw new Error("registerWorkspaceCore requires application singleton().");
29
+ }
30
+
31
+ app.singleton("users.workspace.service", (scope) => {
32
+ const appConfig = resolveAppConfig(scope);
33
+ return createWorkspaceService({
34
+ appConfig,
35
+ workspacesRepository: scope.make("workspacesRepository"),
36
+ workspaceMembershipsRepository: scope.make("workspaceMembershipsRepository"),
37
+ workspaceSettingsRepository: scope.make("workspaceSettingsRepository")
38
+ });
39
+ });
40
+
41
+ app.singleton(USERS_PROFILE_SYNC_SERVICE_TOKEN, (scope) => {
42
+ return createAuthProfileSyncService({
43
+ userProfilesRepository: scope.make("userProfilesRepository"),
44
+ workspaceProvisioningService: scope.make("users.workspace.service")
45
+ });
46
+ });
47
+
48
+ app.singleton(USERS_TENANCY_PROFILE_TOKEN, (scope) => {
49
+ const appConfig = resolveAppConfig(scope);
50
+ return resolveTenancyProfile(appConfig);
51
+ });
52
+
53
+ app.singleton(USERS_WORKSPACE_ENABLED_TOKEN, (scope) => {
54
+ return scope.make(USERS_TENANCY_PROFILE_TOKEN).workspace.enabled === true;
55
+ });
56
+
57
+ app.singleton(USERS_WORKSPACE_SELF_CREATE_ENABLED_TOKEN, (scope) => {
58
+ return scope.make(USERS_TENANCY_PROFILE_TOKEN).workspace.allowSelfCreate === true;
59
+ });
60
+
61
+ app.singleton(USERS_WORKSPACE_TENANCY_ENABLED_TOKEN, (scope) => {
62
+ return scope.make(USERS_TENANCY_PROFILE_TOKEN).mode === TENANCY_MODE_WORKSPACE;
63
+ });
64
+
65
+ app.singleton(USERS_WORKSPACE_INVITATIONS_ENABLED_TOKEN, (scope) => {
66
+ const appConfig = resolveAppConfig(scope);
67
+ const tenancyProfile = scope.make(USERS_TENANCY_PROFILE_TOKEN);
68
+ return resolveWorkspaceInvitationsPolicy({
69
+ appConfig,
70
+ tenancyProfile
71
+ }).enabled;
72
+ });
73
+
74
+ registerActionContextContributor(app, USERS_WORKSPACE_CONTEXT_CONTRIBUTOR_TOKEN, (scope) => {
75
+ return createWorkspaceActionContextContributor({
76
+ workspaceService: scope.make("users.workspace.service")
77
+ });
78
+ });
79
+
80
+ if (typeof app.has !== "function" || !app.has(AUTH_POLICY_CONTEXT_RESOLVER_TOKEN)) {
81
+ app.singleton(AUTH_POLICY_CONTEXT_RESOLVER_TOKEN, (scope) =>
82
+ createWorkspaceAuthPolicyContextResolver({
83
+ workspaceService: scope.make("users.workspace.service")
84
+ })
85
+ );
86
+ }
87
+
88
+ registerRouteVisibilityResolver(app, USERS_WORKSPACE_VISIBILITY_RESOLVER_TOKEN, (scope) =>
89
+ createWorkspaceRouteVisibilityResolver({
90
+ workspaceService: scope.make("users.workspace.service")
91
+ })
92
+ );
93
+ }
94
+
95
+ export { registerWorkspaceCore };
@@ -0,0 +1,16 @@
1
+ import { normalizeObject } from "@jskit-ai/kernel/shared/support/normalize";
2
+
3
+ function resolveRequest(context = {}) {
4
+ const requestMeta = normalizeObject(context?.requestMeta);
5
+ return normalizeObject(requestMeta.request);
6
+ }
7
+
8
+ function resolveWorkspace(context = {}, input = {}) {
9
+ const payload = normalizeObject(input);
10
+ const requestMeta = normalizeObject(context?.requestMeta);
11
+ const resolvedWorkspaceContext = normalizeObject(requestMeta.resolvedWorkspaceContext);
12
+
13
+ return payload.workspace || resolvedWorkspaceContext.workspace || context?.workspace || resolveRequest(context)?.workspace || null;
14
+ }
15
+
16
+ export { resolveWorkspace };
@@ -0,0 +1,135 @@
1
+ import { normalizeSurfaceId } from "@jskit-ai/kernel/shared/surface/registry";
2
+ import { isRecord, normalizeLowerText } from "@jskit-ai/kernel/shared/support/normalize";
3
+ import { resolveDefaultWorkspaceSurfaceId } from "../../shared/support/workspacePathModel.js";
4
+
5
+ const CONSOLE_OWNER_ACCESS_POLICY_ID = "console_owner";
6
+
7
+ function normalizeSurfaceIds(surfaceIds = []) {
8
+ const source = Array.isArray(surfaceIds) ? surfaceIds : [];
9
+ const seen = new Set();
10
+ const normalized = [];
11
+
12
+ for (const candidate of source) {
13
+ const surfaceId = normalizeSurfaceId(candidate);
14
+ if (!surfaceId || seen.has(surfaceId)) {
15
+ continue;
16
+ }
17
+ seen.add(surfaceId);
18
+ normalized.push(surfaceId);
19
+ }
20
+
21
+ return normalized;
22
+ }
23
+
24
+ function resolveWorkspaceSurfaceIdsFromAppConfig(appConfig = {}) {
25
+ return resolveSurfaceIdsFromAppConfig(appConfig, (definition) => definition.requiresWorkspace === true);
26
+ }
27
+
28
+ function resolveConsoleSurfaceIdsFromAppConfig(appConfig = {}) {
29
+ return resolveSurfaceIdsFromAppConfig(appConfig, (definition) => {
30
+ return (
31
+ definition.requiresWorkspace !== true &&
32
+ normalizeLowerText(definition.accessPolicyId) === CONSOLE_OWNER_ACCESS_POLICY_ID
33
+ );
34
+ });
35
+ }
36
+
37
+ function resolveSurfaceIdsFromAppConfig(appConfig = {}, predicate) {
38
+ const source = isRecord(appConfig?.surfaceDefinitions) ? appConfig.surfaceDefinitions : {};
39
+ const resolved = [];
40
+
41
+ for (const [key, value] of Object.entries(source)) {
42
+ const definition = isRecord(value) ? value : {};
43
+ const surfaceId = normalizeSurfaceId(definition.id || key);
44
+ if (!surfaceId) {
45
+ continue;
46
+ }
47
+ if (definition.enabled === false) {
48
+ continue;
49
+ }
50
+ if (typeof predicate === "function" && predicate(definition) === true) {
51
+ resolved.push(surfaceId);
52
+ }
53
+ }
54
+
55
+ return normalizeSurfaceIds(resolved);
56
+ }
57
+
58
+ function materializeWorkspaceActionSurfaces(actions = [], { workspaceSurfaceIds = [] } = {}) {
59
+ const sourceActions = Array.isArray(actions) ? actions : [];
60
+ const resolvedWorkspaceSurfaceIds = normalizeSurfaceIds(workspaceSurfaceIds);
61
+ const materialized = [];
62
+
63
+ for (const entry of sourceActions) {
64
+ const action = isRecord(entry) ? entry : {};
65
+ const surfacesFrom = String(action.surfacesFrom || "")
66
+ .trim()
67
+ .toLowerCase();
68
+ if (surfacesFrom !== "workspace") {
69
+ materialized.push(action);
70
+ continue;
71
+ }
72
+
73
+ if (resolvedWorkspaceSurfaceIds.length < 1) {
74
+ continue;
75
+ }
76
+
77
+ const { surfacesFrom: _ignored, ...rest } = action;
78
+ materialized.push({
79
+ ...rest,
80
+ surfaces: [...resolvedWorkspaceSurfaceIds]
81
+ });
82
+ }
83
+
84
+ return Object.freeze(materialized.map((entry) => Object.freeze({ ...entry })));
85
+ }
86
+
87
+ function registerUsersCoreActionSurfaceSources(app) {
88
+ if (!app || typeof app.actionSurfaceSource !== "function") {
89
+ return;
90
+ }
91
+
92
+ app.actionSurfaceSource("workspace", ({ scope }) => {
93
+ const appConfig = scope?.has?.("appConfig") ? scope.make("appConfig") : {};
94
+ return resolveWorkspaceSurfaceIdsFromAppConfig(appConfig);
95
+ });
96
+
97
+ app.actionSurfaceSource("console", ({ scope }) => {
98
+ const appConfig = scope?.has?.("appConfig") ? scope.make("appConfig") : {};
99
+ return resolveConsoleSurfaceIdsFromAppConfig(appConfig);
100
+ });
101
+ }
102
+
103
+ function materializeWorkspaceActionSurfacesFromAppConfig(actions = [], { appConfig = {} } = {}) {
104
+ const workspaceSurfaceIds = resolveWorkspaceSurfaceIdsFromAppConfig(appConfig);
105
+ return materializeWorkspaceActionSurfaces(actions, { workspaceSurfaceIds });
106
+ }
107
+
108
+ function resolveDefaultWorkspaceRouteSurfaceIdFromAppConfig(appConfig = {}) {
109
+ const workspaceSurfaceIds = resolveWorkspaceSurfaceIdsFromAppConfig(appConfig);
110
+ const workspaceSurfaceSet = new Set(workspaceSurfaceIds);
111
+
112
+ const resolvedSurfaceId = resolveDefaultWorkspaceSurfaceId({
113
+ defaultSurfaceId: appConfig?.surfaceDefaultId,
114
+ workspaceSurfaceIds,
115
+ surfaceRequiresWorkspace(surfaceId) {
116
+ return workspaceSurfaceSet.has(surfaceId);
117
+ }
118
+ });
119
+
120
+ return (
121
+ normalizeSurfaceId(resolvedSurfaceId) ||
122
+ normalizeSurfaceId(workspaceSurfaceIds[0]) ||
123
+ normalizeSurfaceId(appConfig?.surfaceDefaultId) ||
124
+ ""
125
+ );
126
+ }
127
+
128
+ export {
129
+ resolveWorkspaceSurfaceIdsFromAppConfig,
130
+ resolveConsoleSurfaceIdsFromAppConfig,
131
+ resolveDefaultWorkspaceRouteSurfaceIdFromAppConfig,
132
+ materializeWorkspaceActionSurfaces,
133
+ materializeWorkspaceActionSurfacesFromAppConfig,
134
+ registerUsersCoreActionSurfaceSources
135
+ };
@@ -0,0 +1,45 @@
1
+ import {
2
+ TENANCY_MODE_NONE,
3
+ TENANCY_MODE_PERSONAL,
4
+ normalizeTenancyMode
5
+ } from "../../shared/tenancyMode.js";
6
+
7
+ function normalizeWorkspaceInvitationsConfig(appConfig = {}) {
8
+ const source = appConfig && typeof appConfig === "object" && !Array.isArray(appConfig)
9
+ ? appConfig.workspaceInvitations
10
+ : null;
11
+ const normalizedSource = source && typeof source === "object" && !Array.isArray(source)
12
+ ? source
13
+ : {};
14
+
15
+ return Object.freeze({
16
+ enabled: normalizedSource.enabled !== false,
17
+ allowInPersonalMode: normalizedSource.allowInPersonalMode !== false
18
+ });
19
+ }
20
+
21
+ function resolveWorkspaceInvitationsPolicy({
22
+ appConfig = {},
23
+ tenancyProfile = null
24
+ } = {}) {
25
+ const config = normalizeWorkspaceInvitationsConfig(appConfig);
26
+ const normalizedTenancyProfile = tenancyProfile && typeof tenancyProfile === "object"
27
+ ? tenancyProfile
28
+ : {};
29
+ const tenancyMode = normalizeTenancyMode(normalizedTenancyProfile.mode || appConfig?.tenancyMode);
30
+ const workspaceEnabled = normalizedTenancyProfile?.workspace?.enabled === true || tenancyMode !== TENANCY_MODE_NONE;
31
+ const enabledForTenancyMode = tenancyMode !== TENANCY_MODE_PERSONAL || config.allowInPersonalMode === true;
32
+ const enabled = config.enabled === true && workspaceEnabled && enabledForTenancyMode;
33
+
34
+ return Object.freeze({
35
+ enabled,
36
+ workspaceEnabled,
37
+ allowInPersonalMode: config.allowInPersonalMode,
38
+ tenancyMode
39
+ });
40
+ }
41
+
42
+ export {
43
+ normalizeWorkspaceInvitationsConfig,
44
+ resolveWorkspaceInvitationsPolicy
45
+ };
@@ -0,0 +1,22 @@
1
+ import { normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
2
+
3
+ function readWorkspaceSlugFromRouteParams(params = {}) {
4
+ const workspaceSlug = normalizeText(params?.workspaceSlug).toLowerCase();
5
+ return workspaceSlug || "";
6
+ }
7
+
8
+ function buildWorkspaceInputFromRouteParams(params = {}) {
9
+ const workspaceSlug = readWorkspaceSlugFromRouteParams(params);
10
+ if (!workspaceSlug) {
11
+ return {};
12
+ }
13
+
14
+ return {
15
+ workspaceSlug
16
+ };
17
+ }
18
+
19
+ export {
20
+ readWorkspaceSlugFromRouteParams,
21
+ buildWorkspaceInputFromRouteParams
22
+ };