@jskit-ai/workspaces-core 0.1.14 → 0.1.16

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 (83) hide show
  1. package/package.descriptor.mjs +2 -2
  2. package/package.json +18 -3
  3. package/src/server/WorkspacesCoreServiceProvider.js +41 -2
  4. package/src/server/common/contributors/workspaceActionContextContributor.js +88 -0
  5. package/src/server/common/contributors/workspaceAuthPolicyContextResolver.js +34 -0
  6. package/src/server/common/contributors/workspaceRouteVisibilityResolver.js +78 -0
  7. package/src/server/common/formatters/workspaceFormatter.js +53 -0
  8. package/src/server/common/repositories/repositoryUtils.js +59 -0
  9. package/src/server/common/repositories/workspaceInvitesRepository.js +208 -0
  10. package/src/server/common/repositories/workspaceMembershipsRepository.js +190 -0
  11. package/src/server/common/repositories/workspacesRepository.js +202 -0
  12. package/src/server/common/services/workspaceContextService.js +281 -0
  13. package/src/server/common/support/deepFreeze.js +1 -0
  14. package/src/server/common/support/realtimeServiceEvents.js +91 -0
  15. package/src/server/common/support/resolveActionUser.js +9 -0
  16. package/src/server/common/support/workspaceRoutePaths.js +18 -0
  17. package/src/server/common/validators/authenticatedUserValidator.js +43 -0
  18. package/src/server/common/validators/routeParamsValidator.js +62 -0
  19. package/src/server/registerWorkspaceBootstrap.js +27 -0
  20. package/src/server/registerWorkspaceCore.js +100 -0
  21. package/src/server/registerWorkspaceRepositories.js +26 -0
  22. package/src/server/support/resolveWorkspace.js +16 -0
  23. package/src/server/support/workspaceActionSurfaces.js +118 -0
  24. package/src/server/support/workspaceInvitationsPolicy.js +45 -0
  25. package/src/server/support/workspaceRouteInput.js +22 -0
  26. package/src/server/workspaceBootstrapContributor.js +233 -0
  27. package/src/server/workspaceDirectory/bootWorkspaceDirectoryRoutes.js +133 -0
  28. package/src/server/workspaceDirectory/registerWorkspaceDirectory.js +19 -0
  29. package/src/server/workspaceDirectory/workspaceDirectoryActions.js +133 -0
  30. package/src/server/workspaceMembers/bootWorkspaceMembers.js +236 -0
  31. package/src/server/workspaceMembers/registerWorkspaceMembers.js +108 -0
  32. package/src/server/workspaceMembers/workspaceMembersActions.js +186 -0
  33. package/src/server/workspaceMembers/workspaceMembersService.js +222 -0
  34. package/src/server/workspacePendingInvitations/bootWorkspacePendingInvitations.js +62 -0
  35. package/src/server/workspacePendingInvitations/registerWorkspacePendingInvitations.js +119 -0
  36. package/src/server/workspacePendingInvitations/workspacePendingInvitationsActions.js +74 -0
  37. package/src/server/workspacePendingInvitations/workspacePendingInvitationsService.js +138 -0
  38. package/src/server/workspaceSettings/bootWorkspaceSettings.js +76 -0
  39. package/src/server/workspaceSettings/registerWorkspaceSettings.js +62 -0
  40. package/src/server/workspaceSettings/workspaceSettingsActions.js +72 -0
  41. package/src/server/workspaceSettings/workspaceSettingsRepository.js +154 -0
  42. package/src/server/workspaceSettings/workspaceSettingsService.js +66 -0
  43. package/src/shared/operationMessages.js +16 -0
  44. package/src/shared/resources/resolveGlobalArrayRegistry.js +6 -0
  45. package/src/shared/resources/workspaceMembersResource.js +354 -0
  46. package/src/shared/resources/workspacePendingInvitationsResource.js +82 -0
  47. package/src/shared/resources/workspaceResource.js +176 -0
  48. package/src/shared/resources/workspaceSettingsFields.js +59 -0
  49. package/src/shared/resources/workspaceSettingsResource.js +169 -0
  50. package/src/shared/roles.js +161 -0
  51. package/src/shared/settings.js +119 -0
  52. package/src/shared/support/workspacePathModel.js +145 -0
  53. package/src/shared/tenancyMode.js +35 -0
  54. package/src/shared/tenancyProfile.js +73 -0
  55. package/templates/packages/main/src/shared/resources/workspaceSettingsFields.js +2 -2
  56. package/test/registerServiceRealtimeEvents.test.js +116 -0
  57. package/test/registerWorkspaceDirectory.test.js +31 -0
  58. package/test/registerWorkspaceSettings.test.js +40 -0
  59. package/test/repositoryContracts.test.js +34 -0
  60. package/test/resourcesCanonical.test.js +74 -0
  61. package/test/roles.test.js +159 -0
  62. package/test/routeParamsValidator.test.js +49 -0
  63. package/test/settingsFieldRegistriesSingleton.test.js +14 -0
  64. package/test/tenancyProfile.test.js +67 -0
  65. package/test/usersRouteResources.test.js +97 -0
  66. package/test/workspaceActionContextContributor.test.js +344 -0
  67. package/test/workspaceActionSurfaces.test.js +85 -0
  68. package/test/workspaceAuthPolicyContextResolver.test.js +119 -0
  69. package/test/workspaceBootstrapContributor.test.js +169 -0
  70. package/test/workspaceInvitationsPolicy.test.js +71 -0
  71. package/test/workspaceInvitesRepository.test.js +111 -0
  72. package/test/workspaceMembersService.test.js +398 -0
  73. package/test/workspacePathModel.test.js +93 -0
  74. package/test/workspacePendingInvitationsResource.test.js +38 -0
  75. package/test/workspacePendingInvitationsService.test.js +151 -0
  76. package/test/workspaceRouteVisibilityResolver.test.js +83 -0
  77. package/test/workspaceService.test.js +546 -0
  78. package/test/workspaceSettingsActions.test.js +52 -0
  79. package/test/workspaceSettingsRepository.test.js +202 -0
  80. package/test/workspaceSettingsResource.test.js +169 -0
  81. package/test/workspaceSettingsService.test.js +140 -0
  82. package/test/workspacesRouteRequestInputValidator.test.js +5 -5
  83. package/test-support/registerDefaultSettingsFields.js +1 -0
@@ -0,0 +1,91 @@
1
+ import { normalizeRecordId } from "@jskit-ai/kernel/shared/support/normalize";
2
+ import { deepFreeze } from "./deepFreeze.js";
3
+
4
+ function resolveActorScopedEntityId({ options } = {}) {
5
+ return normalizeRecordId(options?.context?.actor?.id, { fallback: "" });
6
+ }
7
+
8
+ function resolveWorkspaceSlugPayload({ args } = {}) {
9
+ return {
10
+ workspaceSlug: String(args?.[0]?.slug || "").trim()
11
+ };
12
+ }
13
+
14
+ const ACCOUNT_SETTINGS_AND_BOOTSTRAP_EVENTS = deepFreeze([
15
+ {
16
+ type: "entity.changed",
17
+ source: "account",
18
+ entity: "settings",
19
+ operation: "updated",
20
+ entityId: resolveActorScopedEntityId,
21
+ realtime: {
22
+ event: "account.settings.changed",
23
+ audience: "actor_user"
24
+ }
25
+ },
26
+ {
27
+ type: "entity.changed",
28
+ source: "users",
29
+ entity: "bootstrap",
30
+ operation: "updated",
31
+ entityId: resolveActorScopedEntityId,
32
+ realtime: {
33
+ event: "users.bootstrap.changed",
34
+ audience: "actor_user"
35
+ }
36
+ }
37
+ ]);
38
+
39
+ function createWorkspaceEntityAndBootstrapEvents({
40
+ workspaceEntity,
41
+ workspaceOperation,
42
+ workspaceRealtimeEvent,
43
+ workspaceEntityId = ({ args }) => args?.[0]?.id,
44
+ bootstrapEntityId = ({ args }) => args?.[0]?.id,
45
+ bootstrapAudience = "event_scope"
46
+ } = {}) {
47
+ const normalizedWorkspaceEntity = String(workspaceEntity || "").trim();
48
+ const normalizedWorkspaceOperation = String(workspaceOperation || "")
49
+ .trim()
50
+ .toLowerCase();
51
+ const normalizedWorkspaceRealtimeEvent = String(workspaceRealtimeEvent || "").trim();
52
+ if (!normalizedWorkspaceEntity || !normalizedWorkspaceOperation || !normalizedWorkspaceRealtimeEvent) {
53
+ throw new Error(
54
+ "createWorkspaceEntityAndBootstrapEvents requires workspaceEntity, workspaceOperation, and workspaceRealtimeEvent."
55
+ );
56
+ }
57
+ if (typeof workspaceEntityId !== "function") {
58
+ throw new Error("createWorkspaceEntityAndBootstrapEvents requires workspaceEntityId to be a function.");
59
+ }
60
+ if (typeof bootstrapEntityId !== "function") {
61
+ throw new Error("createWorkspaceEntityAndBootstrapEvents requires bootstrapEntityId to be a function.");
62
+ }
63
+
64
+ return deepFreeze([
65
+ {
66
+ type: "entity.changed",
67
+ source: "workspace",
68
+ entity: normalizedWorkspaceEntity,
69
+ operation: normalizedWorkspaceOperation,
70
+ entityId: (payload = {}) => normalizeRecordId(workspaceEntityId(payload), { fallback: "" }),
71
+ realtime: {
72
+ event: normalizedWorkspaceRealtimeEvent,
73
+ payload: resolveWorkspaceSlugPayload,
74
+ audience: "event_scope"
75
+ }
76
+ },
77
+ {
78
+ type: "entity.changed",
79
+ source: "users",
80
+ entity: "bootstrap",
81
+ operation: "updated",
82
+ entityId: (payload = {}) => normalizeRecordId(bootstrapEntityId(payload), { fallback: "" }),
83
+ realtime: {
84
+ event: "users.bootstrap.changed",
85
+ audience: bootstrapAudience
86
+ }
87
+ }
88
+ ]);
89
+ }
90
+
91
+ export { ACCOUNT_SETTINGS_AND_BOOTSTRAP_EVENTS, createWorkspaceEntityAndBootstrapEvents };
@@ -0,0 +1,9 @@
1
+ import { normalizeObject } from "@jskit-ai/kernel/shared/support/normalize";
2
+
3
+ function resolveActionUser(context, input) {
4
+ const payload = normalizeObject(input);
5
+ const request = context?.requestMeta?.request || null;
6
+ return payload.user || request?.user || context?.actor || null;
7
+ }
8
+
9
+ export { resolveActionUser };
@@ -0,0 +1,18 @@
1
+ import { resolveScopedApiBasePath } from "@jskit-ai/kernel/shared/surface";
2
+ import { normalizePathname } from "@jskit-ai/kernel/shared/surface/paths";
3
+
4
+ const WORKSPACE_ROUTE_BASE_PATH = resolveScopedApiBasePath({
5
+ routeBase: "/w/:workspaceSlug",
6
+ strictParams: false
7
+ });
8
+
9
+ function resolveWorkspaceRoutePath(relativePath = "/") {
10
+ const normalizedRelativePath = normalizePathname(relativePath || "/");
11
+ if (normalizedRelativePath === "/") {
12
+ return WORKSPACE_ROUTE_BASE_PATH;
13
+ }
14
+
15
+ return `${WORKSPACE_ROUTE_BASE_PATH}${normalizedRelativePath}`;
16
+ }
17
+
18
+ export { WORKSPACE_ROUTE_BASE_PATH, resolveWorkspaceRoutePath };
@@ -0,0 +1,43 @@
1
+ import { Type } from "@fastify/type-provider-typebox";
2
+ import { normalizeObjectInput, recordIdInputSchema } from "@jskit-ai/kernel/shared/validators";
3
+ import { normalizeLowerText, normalizeText } from "@jskit-ai/kernel/shared/actions/textNormalization";
4
+ import { normalizeRecordId } from "@jskit-ai/kernel/shared/support/normalize";
5
+
6
+ function normalizeAuthenticatedUser(input = {}) {
7
+ const source = normalizeObjectInput(input);
8
+ const id = normalizeRecordId(source.id, { fallback: null });
9
+ if (!id) {
10
+ return null;
11
+ }
12
+
13
+ const email = normalizeLowerText(source.email);
14
+ return {
15
+ id,
16
+ email,
17
+ username: normalizeLowerText(source.username),
18
+ displayName: normalizeText(source.displayName) || email || `User ${id}`,
19
+ authProvider: normalizeLowerText(source.authProvider),
20
+ authProviderUserSid: normalizeText(source.authProviderUserSid),
21
+ avatarStorageKey: source.avatarStorageKey ? normalizeText(source.avatarStorageKey) : null,
22
+ avatarVersion: source.avatarVersion == null ? null : String(source.avatarVersion)
23
+ };
24
+ }
25
+
26
+ const authenticatedUserValidator = Object.freeze({
27
+ schema: Type.Object(
28
+ {
29
+ id: recordIdInputSchema,
30
+ email: Type.String({ minLength: 1 }),
31
+ username: Type.Optional(Type.String()),
32
+ displayName: Type.Optional(Type.String()),
33
+ authProvider: Type.Optional(Type.String()),
34
+ authProviderUserSid: Type.Optional(Type.String()),
35
+ avatarStorageKey: Type.Optional(Type.Union([Type.String(), Type.Null()])),
36
+ avatarVersion: Type.Optional(Type.Union([Type.String(), Type.Number(), Type.Null()]))
37
+ },
38
+ { additionalProperties: true }
39
+ ),
40
+ normalize: normalizeAuthenticatedUser
41
+ });
42
+
43
+ export { authenticatedUserValidator };
@@ -0,0 +1,62 @@
1
+ import { Type } from "@fastify/type-provider-typebox";
2
+ import { normalizeObjectInput } from "@jskit-ai/kernel/shared/validators/inputNormalization";
3
+ import { normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
4
+
5
+ function normalizeRouteParams(input = {}) {
6
+ const source = normalizeObjectInput(input);
7
+ const normalized = {};
8
+
9
+ if (Object.hasOwn(source, "workspaceSlug")) {
10
+ normalized.workspaceSlug = normalizeText(source.workspaceSlug).toLowerCase();
11
+ }
12
+
13
+ if (Object.hasOwn(source, "memberUserId")) {
14
+ normalized.memberUserId = normalizeText(source.memberUserId);
15
+ }
16
+
17
+ if (Object.hasOwn(source, "inviteId")) {
18
+ normalized.inviteId = normalizeText(source.inviteId);
19
+ }
20
+
21
+ if (Object.hasOwn(source, "provider")) {
22
+ normalized.provider = normalizeText(source.provider);
23
+ }
24
+
25
+ return normalized;
26
+ }
27
+
28
+ function normalizeWorkspaceSlugParams(input = {}) {
29
+ const source = normalizeObjectInput(input);
30
+ const normalized = {};
31
+
32
+ if (Object.hasOwn(source, "workspaceSlug")) {
33
+ normalized.workspaceSlug = normalizeText(source.workspaceSlug).toLowerCase();
34
+ }
35
+
36
+ return normalized;
37
+ }
38
+
39
+ const routeParamsValidator = Object.freeze({
40
+ schema: Type.Object(
41
+ {
42
+ workspaceSlug: Type.Optional(Type.String({ minLength: 1 })),
43
+ memberUserId: Type.Optional(Type.String({ minLength: 1 })),
44
+ inviteId: Type.Optional(Type.String({ minLength: 1 })),
45
+ provider: Type.Optional(Type.String({ minLength: 1 }))
46
+ },
47
+ { additionalProperties: false }
48
+ ),
49
+ normalize: normalizeRouteParams
50
+ });
51
+
52
+ const workspaceSlugParamsValidator = Object.freeze({
53
+ schema: Type.Object(
54
+ {
55
+ workspaceSlug: Type.Optional(Type.String({ minLength: 1 }))
56
+ },
57
+ { additionalProperties: false }
58
+ ),
59
+ normalize: normalizeWorkspaceSlugParams
60
+ });
61
+
62
+ export { routeParamsValidator, workspaceSlugParamsValidator };
@@ -0,0 +1,27 @@
1
+ import { registerBootstrapPayloadContributor } from "@jskit-ai/kernel/server/runtime";
2
+ import { resolveAppConfig } from "@jskit-ai/kernel/server/support";
3
+ import { createWorkspaceBootstrapContributor } from "./workspaceBootstrapContributor.js";
4
+
5
+
6
+ function registerWorkspaceBootstrap(app) {
7
+ if (!app || typeof app.singleton !== "function") {
8
+ throw new Error("registerWorkspaceBootstrap requires application singleton().");
9
+ }
10
+
11
+ registerBootstrapPayloadContributor(app, "workspaces.core.bootstrap.payloadContributor", (scope) => {
12
+ const workspaceInvitationsEnabled = scope.make("workspaces.invitations.enabled");
13
+
14
+ return createWorkspaceBootstrapContributor({
15
+ workspaceService: scope.make("workspaces.service"),
16
+ workspacePendingInvitationsService: workspaceInvitationsEnabled
17
+ ? scope.make("users.workspace.pending-invitations.service")
18
+ : null,
19
+ workspaceInvitationsEnabled,
20
+ usersRepository: scope.make("usersRepository"),
21
+ appConfig: resolveAppConfig(scope),
22
+ tenancyProfile: scope.make("workspaces.tenancy.profile")
23
+ });
24
+ });
25
+ }
26
+
27
+ export { registerWorkspaceBootstrap };
@@ -0,0 +1,100 @@
1
+ import {
2
+ registerActionContextContributor
3
+ } from "@jskit-ai/kernel/server/actions";
4
+ import { registerRouteVisibilityResolver } from "@jskit-ai/kernel/server/http";
5
+ import { resolveAppConfig } from "@jskit-ai/kernel/server/support";
6
+ import { registerProfileSyncLifecycleContributor } from "@jskit-ai/users-core/server/profileSyncLifecycleContributorRegistry";
7
+ import { createService as createWorkspaceService } from "./common/services/workspaceContextService.js";
8
+ import { createWorkspaceActionContextContributor } from "./common/contributors/workspaceActionContextContributor.js";
9
+ import { createWorkspaceRouteVisibilityResolver } from "./common/contributors/workspaceRouteVisibilityResolver.js";
10
+ import { createWorkspaceAuthPolicyContextResolver } from "./common/contributors/workspaceAuthPolicyContextResolver.js";
11
+ import { TENANCY_MODE_WORKSPACES, resolveTenancyProfile } from "../shared/tenancyProfile.js";
12
+ import { resolveWorkspaceInvitationsPolicy } from "./support/workspaceInvitationsPolicy.js";
13
+ import {
14
+ registerWorkspaceActionSurfaceSources,
15
+ resolveWorkspaceSurfaceIdsFromAppConfig
16
+ } from "./support/workspaceActionSurfaces.js";
17
+
18
+
19
+ function registerWorkspaceCore(app) {
20
+ if (!app || typeof app.singleton !== "function") {
21
+ throw new Error("registerWorkspaceCore requires application singleton().");
22
+ }
23
+
24
+ registerWorkspaceActionSurfaceSources(app);
25
+
26
+ app.singleton("workspaces.tenancy.profile", (scope) => {
27
+ const appConfig = resolveAppConfig(scope);
28
+ return resolveTenancyProfile(appConfig);
29
+ });
30
+
31
+ app.singleton("workspaces.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
+ app.singleton("workspaces.enabled", (scope) => {
41
+ return scope.make("workspaces.tenancy.profile").workspace.enabled === true;
42
+ });
43
+
44
+ app.singleton("workspaces.self-create.enabled", (scope) => {
45
+ return scope.make("workspaces.tenancy.profile").workspace.allowSelfCreate === true;
46
+ });
47
+
48
+ app.singleton("workspaces.tenancy.enabled", (scope) => {
49
+ return scope.make("workspaces.tenancy.profile").mode === TENANCY_MODE_WORKSPACES;
50
+ });
51
+
52
+ app.singleton("workspaces.invitations.enabled", (scope) => {
53
+ const appConfig = resolveAppConfig(scope);
54
+ const tenancyProfile = scope.make("workspaces.tenancy.profile");
55
+ return resolveWorkspaceInvitationsPolicy({
56
+ appConfig,
57
+ tenancyProfile
58
+ }).enabled;
59
+ });
60
+
61
+ registerProfileSyncLifecycleContributor(app, "workspaces.core.profileSyncLifecycleContributor", (scope) => {
62
+ const workspaceService = scope.make("workspaces.service");
63
+
64
+ return Object.freeze({
65
+ contributorId: "workspaces.core.profileSync",
66
+ order: 100,
67
+ async afterIdentityProfileSynced({ profile, created, options } = {}) {
68
+ if (!created || !profile || typeof workspaceService?.provisionWorkspaceForNewUser !== "function") {
69
+ return;
70
+ }
71
+
72
+ await workspaceService.provisionWorkspaceForNewUser(profile, options);
73
+ }
74
+ });
75
+ });
76
+
77
+ registerActionContextContributor(app, "users.core.workspace.actionContextContributor", (scope) => {
78
+ const appConfig = resolveAppConfig(scope);
79
+ return createWorkspaceActionContextContributor({
80
+ workspaceService: scope.make("workspaces.service"),
81
+ workspaceSurfaceIds: resolveWorkspaceSurfaceIdsFromAppConfig(appConfig)
82
+ });
83
+ });
84
+
85
+ if (typeof app.has !== "function" || !app.has("auth.policy.contextResolver")) {
86
+ app.singleton("auth.policy.contextResolver", (scope) =>
87
+ createWorkspaceAuthPolicyContextResolver({
88
+ workspaceService: scope.make("workspaces.service")
89
+ })
90
+ );
91
+ }
92
+
93
+ registerRouteVisibilityResolver(app, "users.core.workspace.routeVisibilityResolver", (scope) =>
94
+ createWorkspaceRouteVisibilityResolver({
95
+ workspaceService: scope.make("workspaces.service")
96
+ })
97
+ );
98
+ }
99
+
100
+ export { registerWorkspaceCore };
@@ -0,0 +1,26 @@
1
+ import { createRepository as createWorkspacesRepository } from "./common/repositories/workspacesRepository.js";
2
+ import { createRepository as createWorkspaceMembershipsRepository } from "./common/repositories/workspaceMembershipsRepository.js";
3
+ import { createRepository as createWorkspaceInvitesRepository } from "./common/repositories/workspaceInvitesRepository.js";
4
+
5
+ function registerWorkspaceRepositories(app) {
6
+ if (!app || typeof app.singleton !== "function") {
7
+ throw new Error("registerWorkspaceRepositories requires application singleton().");
8
+ }
9
+
10
+ app.singleton("workspacesRepository", (scope) => {
11
+ const knex = scope.make("jskit.database.knex");
12
+ return createWorkspacesRepository(knex);
13
+ });
14
+
15
+ app.singleton("workspaceMembershipsRepository", (scope) => {
16
+ const knex = scope.make("jskit.database.knex");
17
+ return createWorkspaceMembershipsRepository(knex);
18
+ });
19
+
20
+ app.singleton("workspaceInvitesRepository", (scope) => {
21
+ const knex = scope.make("jskit.database.knex");
22
+ return createWorkspaceInvitesRepository(knex);
23
+ });
24
+ }
25
+
26
+ export { registerWorkspaceRepositories };
@@ -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,118 @@
1
+ import { normalizeSurfaceId } from "@jskit-ai/kernel/shared/surface/registry";
2
+ import { isRecord } from "@jskit-ai/kernel/shared/support/normalize";
3
+ import { resolveDefaultWorkspaceSurfaceId } from "../../shared/support/workspacePathModel.js";
4
+
5
+ function normalizeSurfaceIds(surfaceIds = []) {
6
+ const source = Array.isArray(surfaceIds) ? surfaceIds : [];
7
+ const seen = new Set();
8
+ const normalized = [];
9
+
10
+ for (const candidate of source) {
11
+ const surfaceId = normalizeSurfaceId(candidate);
12
+ if (!surfaceId || seen.has(surfaceId)) {
13
+ continue;
14
+ }
15
+ seen.add(surfaceId);
16
+ normalized.push(surfaceId);
17
+ }
18
+
19
+ return normalized;
20
+ }
21
+
22
+ function resolveWorkspaceSurfaceIdsFromAppConfig(appConfig = {}) {
23
+ return resolveSurfaceIdsFromAppConfig(appConfig, (definition) => definition.requiresWorkspace === true);
24
+ }
25
+
26
+ function resolveSurfaceIdsFromAppConfig(appConfig = {}, predicate) {
27
+ const source = isRecord(appConfig?.surfaceDefinitions) ? appConfig.surfaceDefinitions : {};
28
+ const resolved = [];
29
+
30
+ for (const [key, value] of Object.entries(source)) {
31
+ const definition = isRecord(value) ? value : {};
32
+ const surfaceId = normalizeSurfaceId(definition.id || key);
33
+ if (!surfaceId) {
34
+ continue;
35
+ }
36
+ if (definition.enabled === false) {
37
+ continue;
38
+ }
39
+ if (typeof predicate === "function" && predicate(definition) === true) {
40
+ resolved.push(surfaceId);
41
+ }
42
+ }
43
+
44
+ return normalizeSurfaceIds(resolved);
45
+ }
46
+
47
+ function materializeWorkspaceActionSurfaces(actions = [], { workspaceSurfaceIds = [] } = {}) {
48
+ const sourceActions = Array.isArray(actions) ? actions : [];
49
+ const resolvedWorkspaceSurfaceIds = normalizeSurfaceIds(workspaceSurfaceIds);
50
+ const materialized = [];
51
+
52
+ for (const entry of sourceActions) {
53
+ const action = isRecord(entry) ? entry : {};
54
+ const surfacesFrom = String(action.surfacesFrom || "")
55
+ .trim()
56
+ .toLowerCase();
57
+ if (surfacesFrom !== "workspace") {
58
+ materialized.push(action);
59
+ continue;
60
+ }
61
+
62
+ if (resolvedWorkspaceSurfaceIds.length < 1) {
63
+ continue;
64
+ }
65
+
66
+ const { surfacesFrom: _ignored, ...rest } = action;
67
+ materialized.push({
68
+ ...rest,
69
+ surfaces: [...resolvedWorkspaceSurfaceIds]
70
+ });
71
+ }
72
+
73
+ return Object.freeze(materialized.map((entry) => Object.freeze({ ...entry })));
74
+ }
75
+
76
+ function registerWorkspaceActionSurfaceSources(app) {
77
+ if (!app || typeof app.actionSurfaceSource !== "function") {
78
+ return;
79
+ }
80
+
81
+ app.actionSurfaceSource("workspace", ({ scope }) => {
82
+ const appConfig = scope?.has?.("appConfig") ? scope.make("appConfig") : {};
83
+ return resolveWorkspaceSurfaceIdsFromAppConfig(appConfig);
84
+ });
85
+ }
86
+
87
+ function materializeWorkspaceActionSurfacesFromAppConfig(actions = [], { appConfig = {} } = {}) {
88
+ const workspaceSurfaceIds = resolveWorkspaceSurfaceIdsFromAppConfig(appConfig);
89
+ return materializeWorkspaceActionSurfaces(actions, { workspaceSurfaceIds });
90
+ }
91
+
92
+ function resolveDefaultWorkspaceRouteSurfaceIdFromAppConfig(appConfig = {}) {
93
+ const workspaceSurfaceIds = resolveWorkspaceSurfaceIdsFromAppConfig(appConfig);
94
+ const workspaceSurfaceSet = new Set(workspaceSurfaceIds);
95
+
96
+ const resolvedSurfaceId = resolveDefaultWorkspaceSurfaceId({
97
+ defaultSurfaceId: appConfig?.surfaceDefaultId,
98
+ workspaceSurfaceIds,
99
+ surfaceRequiresWorkspace(surfaceId) {
100
+ return workspaceSurfaceSet.has(surfaceId);
101
+ }
102
+ });
103
+
104
+ return (
105
+ normalizeSurfaceId(resolvedSurfaceId) ||
106
+ normalizeSurfaceId(workspaceSurfaceIds[0]) ||
107
+ normalizeSurfaceId(appConfig?.surfaceDefaultId) ||
108
+ ""
109
+ );
110
+ }
111
+
112
+ export {
113
+ resolveWorkspaceSurfaceIdsFromAppConfig,
114
+ resolveDefaultWorkspaceRouteSurfaceIdFromAppConfig,
115
+ materializeWorkspaceActionSurfaces,
116
+ materializeWorkspaceActionSurfacesFromAppConfig,
117
+ registerWorkspaceActionSurfaceSources
118
+ };
@@ -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
+ };