@jskit-ai/workspaces-core 0.1.138-oldline.1 → 0.1.138

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 (50) hide show
  1. package/package.json +154 -10
  2. package/patterns/workspace-server/PATTERN.md +60 -0
  3. package/src/server/WorkspacesFeature.js +226 -0
  4. package/src/server/WorkspacesIntegrationsProvider.js +74 -0
  5. package/src/server/common/contributors/workspaceActionContextContributor.js +5 -9
  6. package/src/server/common/contributors/workspaceAuthPolicyContextResolver.js +16 -2
  7. package/src/server/common/contributors/workspaceRouteVisibilityResolver.js +22 -4
  8. package/src/server/common/support/realtimeServiceEvents.js +102 -61
  9. package/src/server/support/workspaceActionSurfaces.js +1 -13
  10. package/src/server/workspaceDirectory/bootWorkspaceDirectoryRoutes.js +5 -11
  11. package/src/server/workspaceDirectory/workspaceDirectoryActions.js +24 -14
  12. package/src/server/workspaceMembers/bootWorkspaceMembers.js +5 -12
  13. package/src/server/workspaceMembers/workspaceMembersActions.js +63 -23
  14. package/src/server/workspacePendingInvitations/bootWorkspacePendingInvitations.js +4 -6
  15. package/src/server/workspacePendingInvitations/workspacePendingInvitationsActions.js +31 -12
  16. package/src/server/workspaceSettings/bootWorkspaceSettings.js +5 -8
  17. package/src/server/workspaceSettings/workspaceSettingsActions.js +26 -8
  18. package/src/server/workspaceSettings/workspaceSettingsRepository.js +4 -4
  19. package/test/exportsContract.test.js +2 -1
  20. package/test/featureRuntime.test.js +116 -0
  21. package/test/packageMetadata.test.js +59 -0
  22. package/test/usersRouteResources.test.js +1 -1
  23. package/test/workspaceActionContextContributor.test.js +7 -12
  24. package/test/workspaceAuthPolicyContextResolver.test.js +30 -0
  25. package/test/workspaceRouteVisibilityResolver.test.js +31 -0
  26. package/test/workspaceService.test.js +2 -2
  27. package/test/workspaceSettingsActions.test.js +103 -17
  28. package/test/workspacesRouteRequestInputValidator.test.js +14 -65
  29. package/package.descriptor.mjs +0 -352
  30. package/src/server/WorkspacesCoreServiceProvider.js +0 -64
  31. package/src/server/registerWorkspaceBootstrap.js +0 -27
  32. package/src/server/registerWorkspaceCore.js +0 -103
  33. package/src/server/registerWorkspaceRepositories.js +0 -30
  34. package/src/server/support/workspaceServerScopeSupport.js +0 -18
  35. package/src/server/workspaceDirectory/registerWorkspaceDirectory.js +0 -19
  36. package/src/server/workspaceMembers/registerWorkspaceMembers.js +0 -137
  37. package/src/server/workspacePendingInvitations/registerWorkspacePendingInvitations.js +0 -130
  38. package/src/server/workspaceSettings/registerWorkspaceSettings.js +0 -66
  39. package/test/packageDescriptor.test.js +0 -91
  40. package/test/registerServiceRealtimeEvents.test.js +0 -116
  41. package/test/registerWorkspaceBootstrap.test.js +0 -86
  42. package/test/registerWorkspaceDirectory.test.js +0 -31
  43. package/test/registerWorkspaceMembers.test.js +0 -140
  44. package/test/registerWorkspaceSettings.test.js +0 -40
  45. package/test/workspaceServerScopeSupport.test.js +0 -131
  46. /package/{templates/migrations → migrations}/workspaces_core_initial.cjs +0 -0
  47. /package/{templates/migrations → migrations}/workspaces_core_workspace_settings_single_name_source.cjs +0 -0
  48. /package/{templates/migrations → migrations}/workspaces_core_workspaces_drop_color.cjs +0 -0
  49. /package/{templates → patterns/workspace-server/example}/config/roles.js +0 -0
  50. /package/{templates → patterns/workspace-server/example}/packages/main/src/server/email/workspaceInviteEmail.js +0 -0
@@ -1,91 +1,132 @@
1
+ import { normalizeDbRecordId } from "@jskit-ai/database-runtime/shared";
2
+ import { createEntityChangedActionEvent } from "@jskit-ai/kernel/server/actions";
1
3
  import { normalizeRecordId } from "@jskit-ai/kernel/shared/support/normalize";
2
- import { deepFreeze } from "./deepFreeze.js";
4
+ import { resolveWorkspace } from "../../support/resolveWorkspace.js";
3
5
 
4
- function resolveActorScopedEntityId({ options } = {}) {
5
- return normalizeRecordId(options?.context?.actor?.id, { fallback: "" });
6
+ function resultValue({ result } = {}) {
7
+ return result?.value ?? result ?? {};
6
8
  }
7
9
 
8
- function resolveWorkspaceSlugPayload({ args } = {}) {
10
+ function actorId({ context } = {}) {
11
+ return context?.actor?.id;
12
+ }
13
+
14
+ function workspaceId(execution = {}) {
15
+ return resolveWorkspace(execution.context, execution.input)?.id || resultValue(execution)?.workspaceId;
16
+ }
17
+
18
+ function workspaceSlugPayload(execution = {}) {
9
19
  return {
10
- workspaceSlug: String(args?.[0]?.slug || "").trim()
20
+ workspaceSlug: String(
21
+ resolveWorkspace(execution.context, execution.input)?.slug || execution.input?.workspaceSlug || ""
22
+ ).trim()
11
23
  };
12
24
  }
13
25
 
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
- }
26
+ const INVITE_RECIPIENT_BOOTSTRAP_AUDIENCE = Object.freeze({
27
+ preset: "event_scope",
28
+ async userQuery({ knex, event } = {}) {
29
+ if (typeof knex !== "function") return [];
30
+ const inviteId = normalizeRecordId(event?.entityId, { fallback: null });
31
+ if (!inviteId) return [];
32
+ const row = await knex("workspace_invites as wi")
33
+ .join("users as up", "up.email", "wi.email")
34
+ .where("wi.id", inviteId)
35
+ .first("up.id as user_id");
36
+ const userId = normalizeDbRecordId(row?.user_id, { fallback: null });
37
+ return userId ? [{ userId }] : [];
36
38
  }
37
- ]);
39
+ });
38
40
 
39
41
  function createWorkspaceEntityAndBootstrapEvents({
40
42
  workspaceEntity,
41
43
  workspaceOperation,
42
44
  workspaceRealtimeEvent,
43
- workspaceEntityId = ({ args }) => args?.[0]?.id,
44
- bootstrapEntityId = ({ args }) => args?.[0]?.id,
45
+ bootstrapEntityId = workspaceId,
45
46
  bootstrapAudience = "event_scope"
46
47
  } = {}) {
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",
48
+ return Object.freeze([
49
+ createEntityChangedActionEvent({
67
50
  source: "workspace",
68
- entity: normalizedWorkspaceEntity,
69
- operation: normalizedWorkspaceOperation,
70
- entityId: (payload = {}) => normalizeRecordId(workspaceEntityId(payload), { fallback: "" }),
51
+ entity: workspaceEntity,
52
+ operation: workspaceOperation,
53
+ entityId: workspaceId,
71
54
  realtime: {
72
- event: normalizedWorkspaceRealtimeEvent,
73
- payload: resolveWorkspaceSlugPayload,
74
- audience: "event_scope"
55
+ event: workspaceRealtimeEvent,
56
+ audience: "event_scope",
57
+ payload: workspaceSlugPayload
75
58
  }
76
- },
77
- {
78
- type: "entity.changed",
59
+ }),
60
+ createEntityChangedActionEvent({
79
61
  source: "users",
80
62
  entity: "bootstrap",
81
63
  operation: "updated",
82
- entityId: (payload = {}) => normalizeRecordId(bootstrapEntityId(payload), { fallback: "" }),
64
+ entityId: bootstrapEntityId,
83
65
  realtime: {
84
66
  event: "users.bootstrap.changed",
85
67
  audience: bootstrapAudience
86
68
  }
69
+ })
70
+ ]);
71
+ }
72
+
73
+ function createActorEvent({ source, entity, realtimeEvent }) {
74
+ return createEntityChangedActionEvent({
75
+ source,
76
+ entity,
77
+ operation: "updated",
78
+ entityId: actorId,
79
+ realtime: { event: realtimeEvent, audience: "actor_user" }
80
+ });
81
+ }
82
+
83
+ function createWorkspaceAudienceEvent({ entity, realtimeEvent }) {
84
+ return createEntityChangedActionEvent({
85
+ source: "workspace",
86
+ entity,
87
+ operation: "updated",
88
+ entityId: ({ result }) => result?.value?.workspaceId ?? result?.workspaceId,
89
+ realtime: {
90
+ event: realtimeEvent,
91
+ audience: ({ result }) => ({ workspaceId: result?.value?.workspaceId ?? result?.workspaceId })
87
92
  }
93
+ });
94
+ }
95
+
96
+ function onlyAccepted(builder) {
97
+ return (execution) => execution.input?.decision === "accept" ? builder(execution) : null;
98
+ }
99
+
100
+ function createInviteDecisionEvents() {
101
+ return Object.freeze([
102
+ createActorEvent({
103
+ source: "workspace",
104
+ entity: "invitation",
105
+ realtimeEvent: "workspace.invitations.pending.changed"
106
+ }),
107
+ createActorEvent({
108
+ source: "users",
109
+ entity: "bootstrap",
110
+ realtimeEvent: "users.bootstrap.changed"
111
+ }),
112
+ onlyAccepted(createActorEvent({
113
+ source: "workspace",
114
+ entity: "directory",
115
+ realtimeEvent: "workspaces.changed"
116
+ })),
117
+ onlyAccepted(createWorkspaceAudienceEvent({
118
+ entity: "member",
119
+ realtimeEvent: "workspace.members.changed"
120
+ })),
121
+ createWorkspaceAudienceEvent({
122
+ entity: "invite",
123
+ realtimeEvent: "workspace.invites.changed"
124
+ })
88
125
  ]);
89
126
  }
90
127
 
91
- export { ACCOUNT_SETTINGS_AND_BOOTSTRAP_EVENTS, createWorkspaceEntityAndBootstrapEvents };
128
+ export {
129
+ INVITE_RECIPIENT_BOOTSTRAP_AUDIENCE,
130
+ createInviteDecisionEvents,
131
+ createWorkspaceEntityAndBootstrapEvents
132
+ };
@@ -89,17 +89,6 @@ function materializeWorkspaceActionSurfaces(actions = [], { workspaceSurfaceIds
89
89
  return Object.freeze(materialized.map((entry) => Object.freeze({ ...entry })));
90
90
  }
91
91
 
92
- function registerWorkspaceActionSurfaceSources(app) {
93
- if (!app || typeof app.actionSurfaceSource !== "function") {
94
- return;
95
- }
96
-
97
- app.actionSurfaceSource("workspace", ({ scope }) => {
98
- const appConfig = scope?.has?.("appConfig") ? scope.make("appConfig") : {};
99
- return resolveWorkspaceSurfaceIdsFromAppConfig(appConfig);
100
- });
101
- }
102
-
103
92
  function materializeWorkspaceActionSurfacesFromAppConfig(actions = [], { appConfig = {} } = {}) {
104
93
  const workspaceSurfaceIds = resolveWorkspaceSurfaceIdsFromAppConfig(appConfig);
105
94
  return materializeWorkspaceActionSurfaces(actions, { workspaceSurfaceIds });
@@ -130,6 +119,5 @@ export {
130
119
  resolveWorkspaceMembershipOptionalSurfaceIdsFromAppConfig,
131
120
  resolveDefaultWorkspaceRouteSurfaceIdFromAppConfig,
132
121
  materializeWorkspaceActionSurfaces,
133
- materializeWorkspaceActionSurfacesFromAppConfig,
134
- registerWorkspaceActionSurfaceSources
122
+ materializeWorkspaceActionSurfacesFromAppConfig
135
123
  };
@@ -25,17 +25,11 @@ function resolveWorkspaceRecordId(record = {}, context = {}) {
25
25
  throw new Error("Workspace JSON:API response requires workspace id.");
26
26
  }
27
27
 
28
- function bootWorkspaceDirectoryRoutes(app) {
29
- if (!app || typeof app.make !== "function" || typeof app.has !== "function") {
30
- throw new Error("bootWorkspaceDirectoryRoutes requires application make()/has().");
28
+ function registerWorkspaceDirectoryRoutes(router, { config = {}, workspaceSelfCreateEnabled = false } = {}) {
29
+ if (!router || typeof router.register !== "function") {
30
+ throw new TypeError("registerWorkspaceDirectoryRoutes requires router.register().");
31
31
  }
32
-
33
- const router = app.make("jskit.http.router");
34
- const appConfig = app.has("appConfig") ? app.make("appConfig") : {};
35
- const workspaceRouteSurfaceId = resolveDefaultWorkspaceRouteSurfaceIdFromAppConfig(appConfig);
36
- const workspaceSelfCreateEnabled = app.has("workspaces.self-create.enabled")
37
- ? app.make("workspaces.self-create.enabled") === true
38
- : false;
32
+ const workspaceRouteSurfaceId = resolveDefaultWorkspaceRouteSurfaceIdFromAppConfig(config);
39
33
 
40
34
  if (workspaceSelfCreateEnabled) {
41
35
  router.register(
@@ -157,4 +151,4 @@ function bootWorkspaceDirectoryRoutes(app) {
157
151
  );
158
152
  }
159
153
 
160
- export { bootWorkspaceDirectoryRoutes };
154
+ export { registerWorkspaceDirectoryRoutes };
@@ -16,13 +16,13 @@ const workspaceUpdateInputValidator = composeSchemaDefinitions([
16
16
  context: "workspaceDirectoryActions.workspaceUpdateInputValidator"
17
17
  });
18
18
 
19
- const workspaceDirectoryActions = Object.freeze([
19
+ const workspaceDirectoryActionSpecifications = Object.freeze([
20
20
  {
21
21
  id: "workspace.workspaces.create",
22
22
  version: 1,
23
23
  kind: "command",
24
24
  channels: ["api", "assistant_tool", "automation", "internal"],
25
- surfacesFrom: "enabled",
25
+ surfaces: ["*"],
26
26
  permission: {
27
27
  require: "authenticated"
28
28
  },
@@ -38,8 +38,8 @@ const workspaceDirectoryActions = Object.freeze([
38
38
  description: "Create a workspace for the authenticated user."
39
39
  }
40
40
  },
41
- async execute(input, context, deps) {
42
- return returnJsonApiData(await deps.workspaceService.createWorkspaceForAuthenticatedUser(resolveActionUser(context, input), input, {
41
+ async run(workspaceService, input, context) {
42
+ return returnJsonApiData(await workspaceService.createWorkspaceForAuthenticatedUser(resolveActionUser(context, input), input, {
43
43
  request: resolveRequest(context),
44
44
  context
45
45
  }));
@@ -50,7 +50,7 @@ const workspaceDirectoryActions = Object.freeze([
50
50
  version: 1,
51
51
  kind: "query",
52
52
  channels: ["api", "automation", "internal"],
53
- surfacesFrom: "enabled",
53
+ surfaces: ["*"],
54
54
  permission: {
55
55
  require: "authenticated"
56
56
  },
@@ -61,9 +61,9 @@ const workspaceDirectoryActions = Object.freeze([
61
61
  actionName: "workspace.workspaces.list"
62
62
  },
63
63
  observability: {},
64
- async execute(input, context, deps) {
64
+ async run(workspaceService, input, context) {
65
65
  return returnJsonApiData({
66
- items: await deps.workspaceService.listWorkspacesForAuthenticatedUser(resolveActionUser(context, input), {
66
+ items: await workspaceService.listWorkspacesForAuthenticatedUser(resolveActionUser(context, input), {
67
67
  request: resolveRequest(context),
68
68
  context
69
69
  }),
@@ -76,7 +76,7 @@ const workspaceDirectoryActions = Object.freeze([
76
76
  version: 1,
77
77
  kind: "query",
78
78
  channels: ["api", "automation", "internal"],
79
- surfacesFrom: "workspace",
79
+ surfaces: ["*"],
80
80
  permission: {
81
81
  require: "any",
82
82
  permissions: ["workspace.settings.view", "workspace.settings.update"]
@@ -88,8 +88,8 @@ const workspaceDirectoryActions = Object.freeze([
88
88
  actionName: "workspace.workspaces.read"
89
89
  },
90
90
  observability: {},
91
- async execute(input, context, deps) {
92
- return returnJsonApiData(await deps.workspaceService.getWorkspaceForAuthenticatedUser(
91
+ async run(workspaceService, input, context) {
92
+ return returnJsonApiData(await workspaceService.getWorkspaceForAuthenticatedUser(
93
93
  resolveActionUser(context, input),
94
94
  input.workspaceSlug,
95
95
  {
@@ -104,7 +104,7 @@ const workspaceDirectoryActions = Object.freeze([
104
104
  version: 1,
105
105
  kind: "command",
106
106
  channels: ["api", "assistant_tool", "automation", "internal"],
107
- surfacesFrom: "workspace",
107
+ surfaces: ["*"],
108
108
  permission: {
109
109
  require: "all",
110
110
  permissions: ["workspace.settings.update"]
@@ -121,9 +121,9 @@ const workspaceDirectoryActions = Object.freeze([
121
121
  description: "Update workspace profile fields."
122
122
  }
123
123
  },
124
- async execute(input, context, deps) {
124
+ async run(workspaceService, input, context) {
125
125
  const { workspaceSlug, ...patch } = input;
126
- return returnJsonApiData(await deps.workspaceService.updateWorkspaceForAuthenticatedUser(
126
+ return returnJsonApiData(await workspaceService.updateWorkspaceForAuthenticatedUser(
127
127
  resolveActionUser(context, input),
128
128
  workspaceSlug,
129
129
  patch,
@@ -136,4 +136,14 @@ const workspaceDirectoryActions = Object.freeze([
136
136
  }
137
137
  ]);
138
138
 
139
- export { workspaceDirectoryActions };
139
+ function buildWorkspaceDirectoryActions({ workspaceService } = {}) {
140
+ if (!workspaceService) throw new TypeError("buildWorkspaceDirectoryActions requires workspaceService.");
141
+ return workspaceDirectoryActionSpecifications.map(({ run, ...definition }) => Object.freeze({
142
+ ...definition,
143
+ execute(input, context) {
144
+ return run(workspaceService, input, context);
145
+ }
146
+ }));
147
+ }
148
+
149
+ export { workspaceDirectoryActionSpecifications, buildWorkspaceDirectoryActions };
@@ -28,18 +28,11 @@ function resolveWorkspaceAggregateRecordId(record = {}, context = {}) {
28
28
  throw new Error("Workspace JSON:API response requires workspace id.");
29
29
  }
30
30
 
31
- function bootWorkspaceMembers(app) {
32
- if (!app || typeof app.make !== "function") {
33
- throw new Error("bootWorkspaceMembers requires application make().");
31
+ function registerWorkspaceMembersRoutes(router, { config = {}, workspaceInvitationsEnabled = false } = {}) {
32
+ if (!router || typeof router.register !== "function") {
33
+ throw new TypeError("registerWorkspaceMembersRoutes requires router.register().");
34
34
  }
35
-
36
- const router = app.make("jskit.http.router");
37
- const appConfig = typeof app.has === "function" && app.has("appConfig") ? app.make("appConfig") : {};
38
- const workspaceInvitationsEnabled =
39
- typeof app.has === "function" && app.has("workspaces.invitations.enabled")
40
- ? app.make("workspaces.invitations.enabled") === true
41
- : false;
42
- const workspaceRouteSurfaceId = resolveDefaultWorkspaceRouteSurfaceIdFromAppConfig(appConfig);
35
+ const workspaceRouteSurfaceId = resolveDefaultWorkspaceRouteSurfaceIdFromAppConfig(config);
43
36
 
44
37
  router.register(
45
38
  "GET",
@@ -271,4 +264,4 @@ function bootWorkspaceMembers(app) {
271
264
  }
272
265
  }
273
266
 
274
- export { bootWorkspaceMembers };
267
+ export { registerWorkspaceMembersRoutes };
@@ -3,8 +3,34 @@ import { returnJsonApiData } from "@jskit-ai/http-runtime/shared";
3
3
  import { resolveWorkspace } from "../support/resolveWorkspace.js";
4
4
  import { resolveActionUser } from "../common/support/resolveActionUser.js";
5
5
  import { workspaceMembersResource } from "../../shared/resources/workspaceMembersResource.js";
6
+ import {
7
+ INVITE_RECIPIENT_BOOTSTRAP_AUDIENCE,
8
+ createWorkspaceEntityAndBootstrapEvents
9
+ } from "../common/support/realtimeServiceEvents.js";
6
10
  import { workspaceSlugParamsValidator } from "../common/validators/routeParamsValidator.js";
7
11
 
12
+ const WORKSPACE_MEMBER_CHANGED_EVENTS = createWorkspaceEntityAndBootstrapEvents({
13
+ workspaceEntity: "member",
14
+ workspaceOperation: "updated",
15
+ workspaceRealtimeEvent: "workspace.members.changed"
16
+ });
17
+
18
+ const WORKSPACE_INVITE_CREATED_EVENTS = createWorkspaceEntityAndBootstrapEvents({
19
+ workspaceEntity: "invite",
20
+ workspaceOperation: "created",
21
+ workspaceRealtimeEvent: "workspace.invites.changed",
22
+ bootstrapEntityId: ({ result } = {}) => result?.value?.createdInviteId,
23
+ bootstrapAudience: INVITE_RECIPIENT_BOOTSTRAP_AUDIENCE
24
+ });
25
+
26
+ const WORKSPACE_INVITE_REVOKED_EVENTS = createWorkspaceEntityAndBootstrapEvents({
27
+ workspaceEntity: "invite",
28
+ workspaceOperation: "updated",
29
+ workspaceRealtimeEvent: "workspace.invites.changed",
30
+ bootstrapEntityId: ({ result } = {}) => result?.value?.revokedInviteId,
31
+ bootstrapAudience: INVITE_RECIPIENT_BOOTSTRAP_AUDIENCE
32
+ });
33
+
8
34
  const updateMemberRoleActionInput = composeSchemaDefinitions([
9
35
  workspaceSlugParamsValidator,
10
36
  workspaceMembersResource.operations.updateMemberRole.input
@@ -37,13 +63,13 @@ const revokeInviteActionInput = composeSchemaDefinitions([
37
63
  context: "workspaceMembersActions.revokeInviteActionInput"
38
64
  });
39
65
 
40
- const workspaceMembersActions = Object.freeze([
66
+ const workspaceMembersActionSpecifications = Object.freeze([
41
67
  {
42
68
  id: "workspace.roles.list",
43
69
  version: 1,
44
70
  kind: "query",
45
71
  channels: ["api", "automation", "internal"],
46
- surfacesFrom: "workspace",
72
+ surfaces: ["*"],
47
73
  permission: {
48
74
  require: "all",
49
75
  permissions: ["workspace.roles.view"]
@@ -55,8 +81,8 @@ const workspaceMembersActions = Object.freeze([
55
81
  actionName: "workspace.roles.list"
56
82
  },
57
83
  observability: {},
58
- async execute(_input, context, deps) {
59
- return returnJsonApiData(await deps.workspaceMembersService.listRoles({ context }));
84
+ async run(workspaceMembersService, _input, context) {
85
+ return returnJsonApiData(await workspaceMembersService.listRoles({ context }));
60
86
  }
61
87
  },
62
88
  {
@@ -64,7 +90,7 @@ const workspaceMembersActions = Object.freeze([
64
90
  version: 1,
65
91
  kind: "query",
66
92
  channels: ["api", "automation", "internal"],
67
- surfacesFrom: "workspace",
93
+ surfaces: ["*"],
68
94
  permission: {
69
95
  require: "all",
70
96
  permissions: ["workspace.members.view"]
@@ -76,8 +102,8 @@ const workspaceMembersActions = Object.freeze([
76
102
  actionName: "workspace.members.list"
77
103
  },
78
104
  observability: {},
79
- async execute(input, context, deps) {
80
- return returnJsonApiData(await deps.workspaceMembersService.listMembers(resolveWorkspace(context, input), {
105
+ async run(workspaceMembersService, input, context) {
106
+ return returnJsonApiData(await workspaceMembersService.listMembers(resolveWorkspace(context, input), {
81
107
  context
82
108
  }));
83
109
  }
@@ -87,7 +113,7 @@ const workspaceMembersActions = Object.freeze([
87
113
  version: 1,
88
114
  kind: "command",
89
115
  channels: ["api", "automation", "internal"],
90
- surfacesFrom: "workspace",
116
+ surfaces: ["*"],
91
117
  permission: {
92
118
  require: "all",
93
119
  permissions: ["workspace.members.manage"]
@@ -99,8 +125,9 @@ const workspaceMembersActions = Object.freeze([
99
125
  actionName: "workspace.member.role.update"
100
126
  },
101
127
  observability: {},
102
- async execute(input, context, deps) {
103
- return returnJsonApiData(await deps.workspaceMembersService.updateMemberRole(resolveWorkspace(context, input), {
128
+ events: WORKSPACE_MEMBER_CHANGED_EVENTS,
129
+ async run(workspaceMembersService, input, context) {
130
+ return returnJsonApiData(await workspaceMembersService.updateMemberRole(resolveWorkspace(context, input), {
104
131
  memberUserId: input.memberUserId,
105
132
  roleSid: input.roleSid
106
133
  }, {
@@ -113,7 +140,7 @@ const workspaceMembersActions = Object.freeze([
113
140
  version: 1,
114
141
  kind: "command",
115
142
  channels: ["api", "automation", "internal"],
116
- surfacesFrom: "workspace",
143
+ surfaces: ["*"],
117
144
  permission: {
118
145
  require: "all",
119
146
  permissions: ["workspace.members.manage"]
@@ -125,8 +152,9 @@ const workspaceMembersActions = Object.freeze([
125
152
  actionName: "workspace.member.remove"
126
153
  },
127
154
  observability: {},
128
- async execute(input, context, deps) {
129
- return returnJsonApiData(await deps.workspaceMembersService.removeMember(resolveWorkspace(context, input), {
155
+ events: WORKSPACE_MEMBER_CHANGED_EVENTS,
156
+ async run(workspaceMembersService, input, context) {
157
+ return returnJsonApiData(await workspaceMembersService.removeMember(resolveWorkspace(context, input), {
130
158
  memberUserId: input.memberUserId
131
159
  }, {
132
160
  context
@@ -138,7 +166,7 @@ const workspaceMembersActions = Object.freeze([
138
166
  version: 1,
139
167
  kind: "query",
140
168
  channels: ["api", "automation", "internal"],
141
- surfacesFrom: "workspace",
169
+ surfaces: ["*"],
142
170
  permission: {
143
171
  require: "all",
144
172
  permissions: ["workspace.members.view"]
@@ -150,8 +178,8 @@ const workspaceMembersActions = Object.freeze([
150
178
  actionName: "workspace.invites.list"
151
179
  },
152
180
  observability: {},
153
- async execute(input, context, deps) {
154
- return returnJsonApiData(await deps.workspaceMembersService.listInvites(resolveWorkspace(context, input), {
181
+ async run(workspaceMembersService, input, context) {
182
+ return returnJsonApiData(await workspaceMembersService.listInvites(resolveWorkspace(context, input), {
155
183
  context
156
184
  }));
157
185
  }
@@ -161,7 +189,7 @@ const workspaceMembersActions = Object.freeze([
161
189
  version: 1,
162
190
  kind: "command",
163
191
  channels: ["api", "assistant_tool", "automation", "internal"],
164
- surfacesFrom: "workspace",
192
+ surfaces: ["*"],
165
193
  permission: {
166
194
  require: "all",
167
195
  permissions: ["workspace.members.invite"]
@@ -173,13 +201,14 @@ const workspaceMembersActions = Object.freeze([
173
201
  actionName: "workspace.invite.create"
174
202
  },
175
203
  observability: {},
204
+ events: WORKSPACE_INVITE_CREATED_EVENTS,
176
205
  extensions: {
177
206
  assistant: {
178
207
  description: "Invite a person to the workspace."
179
208
  }
180
209
  },
181
- async execute(input, context, deps) {
182
- return returnJsonApiData(await deps.workspaceMembersService.createInvite(
210
+ async run(workspaceMembersService, input, context) {
211
+ return returnJsonApiData(await workspaceMembersService.createInvite(
183
212
  resolveWorkspace(context, input),
184
213
  resolveActionUser(context, input),
185
214
  {
@@ -197,7 +226,7 @@ const workspaceMembersActions = Object.freeze([
197
226
  version: 1,
198
227
  kind: "command",
199
228
  channels: ["api", "automation", "internal"],
200
- surfacesFrom: "workspace",
229
+ surfaces: ["*"],
201
230
  permission: {
202
231
  require: "all",
203
232
  permissions: ["workspace.invites.revoke"]
@@ -209,12 +238,23 @@ const workspaceMembersActions = Object.freeze([
209
238
  actionName: "workspace.invite.revoke"
210
239
  },
211
240
  observability: {},
212
- async execute(input, context, deps) {
213
- return returnJsonApiData(await deps.workspaceMembersService.revokeInvite(resolveWorkspace(context, input), input.inviteId, {
241
+ events: WORKSPACE_INVITE_REVOKED_EVENTS,
242
+ async run(workspaceMembersService, input, context) {
243
+ return returnJsonApiData(await workspaceMembersService.revokeInvite(resolveWorkspace(context, input), input.inviteId, {
214
244
  context
215
245
  }));
216
246
  }
217
247
  }
218
248
  ]);
219
249
 
220
- export { workspaceMembersActions };
250
+ function buildWorkspaceMembersActions({ workspaceMembersService } = {}) {
251
+ if (!workspaceMembersService) throw new TypeError("buildWorkspaceMembersActions requires workspaceMembersService.");
252
+ return workspaceMembersActionSpecifications.map(({ run, ...definition }) => Object.freeze({
253
+ ...definition,
254
+ execute(input, context) {
255
+ return run(workspaceMembersService, input, context);
256
+ }
257
+ }));
258
+ }
259
+
260
+ export { workspaceMembersActionSpecifications, buildWorkspaceMembersActions };
@@ -24,13 +24,11 @@ function resolveInviteResolutionRecordId(record = {}) {
24
24
  return "workspace-invitation";
25
25
  }
26
26
 
27
- function bootWorkspacePendingInvitations(app) {
28
- if (!app || typeof app.make !== "function") {
29
- throw new Error("bootWorkspacePendingInvitations requires application make().");
27
+ function registerWorkspacePendingInvitationsRoutes(router) {
28
+ if (!router || typeof router.register !== "function") {
29
+ throw new TypeError("registerWorkspacePendingInvitationsRoutes requires router.register().");
30
30
  }
31
31
 
32
- const router = app.make("jskit.http.router");
33
-
34
32
  router.register(
35
33
  "GET",
36
34
  "/api/workspace/invitations/resolve",
@@ -110,4 +108,4 @@ function bootWorkspacePendingInvitations(app) {
110
108
  );
111
109
  }
112
110
 
113
- export { bootWorkspacePendingInvitations };
111
+ export { registerWorkspacePendingInvitationsRoutes };