@jskit-ai/workspaces-core 0.2.4 → 0.2.6

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 (75) hide show
  1. package/locales/en.json +2 -2
  2. package/package.json +38 -11
  3. package/patterns/workspace-server/PATTERN.md +42 -17
  4. package/patterns/workspace-server/example/packages/main/src/server/WorkspaceInvitationsFeature.js +27 -0
  5. package/patterns/workspace-server/example/packages/main/src/server/email/workspaceInviteEmail.js +11 -10
  6. package/patterns/workspace-server/example/packages/main/src/server/email/workspaceInviteMailer.js +27 -0
  7. package/src/server/WorkspacesFeature.js +74 -183
  8. package/src/server/WorkspacesIntegrationsProvider.js +26 -10
  9. package/src/server/common/contributors/workspaceActionContextContributor.js +60 -70
  10. package/src/server/common/contributors/workspaceAuthPolicyContextResolver.js +12 -27
  11. package/src/server/common/contributors/workspaceRouteVisibilityResolver.js +48 -63
  12. package/src/server/common/formatters/workspaceFormatter.js +12 -10
  13. package/src/server/common/repositories/workspaceInvitesRepository.js +74 -75
  14. package/src/server/common/repositories/workspaceMembershipsRepository.js +23 -45
  15. package/src/server/common/repositories/workspacesRepository.js +17 -25
  16. package/src/server/common/services/{workspaceContextService.js → workspaceDirectoryService.js} +52 -72
  17. package/src/server/common/support/realtimeServiceEvents.js +20 -12
  18. package/src/server/common/support/workspaceRecordId.js +21 -0
  19. package/src/server/common/validators/routeParamsValidator.js +3 -2
  20. package/src/server/i18n.js +5 -2
  21. package/src/server/support/resolveWorkspace.js +10 -7
  22. package/src/server/support/workspaceActionSurfaces.js +5 -41
  23. package/src/server/support/workspaceCrud.js +25 -0
  24. package/src/server/support/workspaceInvitationsPolicy.js +16 -27
  25. package/src/server/support/workspaceRouteInput.js +19 -2
  26. package/src/server/{common/support → support}/workspaceRoutePaths.js +8 -2
  27. package/src/server/types.js +47 -0
  28. package/src/server/workspaceBootstrapContributor.js +105 -142
  29. package/src/server/workspaceDirectory/workspaceDirectoryActions.js +105 -137
  30. package/src/server/workspaceDirectory/{bootWorkspaceDirectoryRoutes.js → workspaceDirectoryRoutes.js} +7 -19
  31. package/src/server/workspaceMembers/defaultWorkspaceInviteEmail.js +15 -29
  32. package/src/server/workspaceMembers/unconfiguredWorkspaceInviteMailer.js +25 -0
  33. package/src/server/workspaceMembers/workspaceInviteUrls.js +24 -23
  34. package/src/server/workspaceMembers/workspaceMembersActions.js +189 -219
  35. package/src/server/workspaceMembers/{bootWorkspaceMembers.js → workspaceMembersRoutes.js} +14 -26
  36. package/src/server/workspaceMembers/workspaceMembersService.js +110 -97
  37. package/src/server/workspacePendingInvitations/noInviteAcceptanceParticipant.js +8 -0
  38. package/src/server/workspacePendingInvitations/workspacePendingInvitationsActions.js +79 -112
  39. package/src/server/workspacePendingInvitations/{bootWorkspacePendingInvitations.js → workspacePendingInvitationsRoutes.js} +11 -8
  40. package/src/server/workspacePendingInvitations/workspacePendingInvitationsService.js +54 -57
  41. package/src/server/workspaceSettings/workspaceSettingsActions.js +53 -73
  42. package/src/server/workspaceSettings/workspaceSettingsRepository.js +20 -20
  43. package/src/server/workspaceSettings/{bootWorkspaceSettings.js → workspaceSettingsRoutes.js} +9 -21
  44. package/src/server/workspaceSettings/workspaceSettingsService.js +24 -26
  45. package/src/server/workspacesRuntime.js +185 -0
  46. package/src/shared/operationMessages.js +6 -15
  47. package/src/shared/resources/workspaceMembersResource.js +2 -2
  48. package/src/shared/resources/workspacePendingInvitationsResource.js +2 -2
  49. package/src/shared/resources/workspaceResource.js +4 -1
  50. package/src/shared/roles.js +15 -12
  51. package/src/shared/tenancyProfile.js +2 -2
  52. package/test/exportsContract.test.js +5 -0
  53. package/test/featureRuntime.test.js +5 -6
  54. package/test/packageMetadata.test.js +12 -0
  55. package/test/repositories.integration.test.js +1 -1
  56. package/test/requestedWorkspaceScope.test.js +24 -0
  57. package/test/roleLabels.test.js +29 -7
  58. package/test/support/invitationTransactions.js +35 -19
  59. package/test/workspaceActionSurfaces.test.js +0 -55
  60. package/test/workspaceAuthPolicyContextResolver.test.js +1 -1
  61. package/test/workspaceBootstrapContributor.test.js +7 -6
  62. package/test/workspaceCrud.test.js +23 -0
  63. package/test/workspaceInvitesRepository.test.js +9 -8
  64. package/test/workspaceMembersService.test.js +131 -28
  65. package/test/workspacePendingInvitationsResource.test.js +2 -2
  66. package/test/workspacePendingInvitationsService.test.js +69 -22
  67. package/test/workspaceService.test.js +58 -47
  68. package/test/workspaceSettingsActions.test.js +14 -7
  69. package/test/workspaceSettingsRepository.test.js +11 -0
  70. package/test/workspaceSettingsService.test.js +11 -9
  71. package/test/workspacesRepository.test.js +16 -16
  72. package/test/workspacesRouteRequestInputValidator.test.js +4 -4
  73. package/src/server/common/repositories/repositoryUtils.js +0 -43
  74. package/src/server/common/support/deepFreeze.js +0 -1
  75. package/src/server/common/support/resolveActionUser.js +0 -9
@@ -1,12 +1,8 @@
1
- import {
2
- emptyInputValidator,
3
- resolveRequest
4
- } from "@jskit-ai/kernel/server/actions/actionContributorHelpers";
5
- import { returnJsonApiData } from "@jskit-ai/http-runtime/shared";
1
+ import { emptyInputValidator } from "@jskit-ai/kernel/server/actions/actionContributorHelpers";
6
2
  import { composeSchemaDefinitions } from "@jskit-ai/kernel/shared/validators";
7
3
  import { workspaceResource } from "../../shared/resources/workspaceResource.js";
8
4
  import { workspaceSlugParamsValidator } from "../common/validators/routeParamsValidator.js";
9
- import { resolveActionUser } from "../common/support/resolveActionUser.js";
5
+ import { resolveActionUser } from "@jskit-ai/users-core/server/resolveActionUser";
10
6
 
11
7
  function workspaceAssistantSummary({ id, slug, name, ownerUserId, avatarUrl }) {
12
8
  return { id, slug, name, ownerUserId, avatarUrl };
@@ -20,140 +16,112 @@ const workspaceUpdateInputValidator = composeSchemaDefinitions([
20
16
  context: "workspaceDirectoryActions.workspaceUpdateInputValidator"
21
17
  });
22
18
 
23
- const workspaceDirectoryActionSpecifications = Object.freeze([
24
- {
25
- id: "workspace.workspaces.create",
26
- version: 1,
27
- kind: "command",
28
- channels: ["api", "assistant_tool", "automation", "internal"],
29
- surfaces: ["*"],
30
- permission: {
31
- require: "authenticated"
32
- },
33
- input: workspaceResource.operations.create.body,
34
- output: null,
35
- extensions: {
36
- assistant: {
37
- description: "Create a workspace for the signed-in user.",
38
- output: workspaceResource.operations.create.output,
39
- transformResult: (result) => workspaceAssistantSummary(result.value)
40
- }
41
- },
42
- idempotency: "none",
43
- async run(workspaceService, input, context) {
44
- return returnJsonApiData(await workspaceService.createWorkspaceForAuthenticatedUser(resolveActionUser(context, input), input, {
45
- request: resolveRequest(context),
46
- context
47
- }));
48
- }
49
- },
50
- {
51
- id: "workspace.workspaces.list",
52
- version: 1,
53
- kind: "query",
54
- channels: ["api", "automation", "internal"],
55
- surfaces: ["*"],
56
- permission: {
57
- require: "authenticated"
58
- },
59
- input: emptyInputValidator,
60
- output: null,
61
- extensions: {
62
- assistant: {
63
- description: "List workspaces accessible to the signed-in user.",
64
- output: workspaceResource.operations.list.output,
65
- transformResult: (result) => ({
66
- items: result.value.items.map(({ id, slug, name, avatarUrl, roleSid, isAccessible }) =>
67
- ({ id, slug, name, avatarUrl, roleSid, isAccessible })),
68
- nextCursor: result.value.nextCursor
69
- })
70
- }
71
- },
72
- idempotency: "none",
73
- async run(workspaceService, input, context) {
74
- return returnJsonApiData({
75
- items: await workspaceService.listWorkspacesForAuthenticatedUser(resolveActionUser(context, input), {
76
- request: resolveRequest(context),
77
- context
78
- }),
79
- nextCursor: null
80
- });
81
- }
82
- },
83
- {
84
- id: "workspace.workspaces.read",
85
- version: 1,
86
- kind: "query",
87
- channels: ["api", "automation", "internal"],
88
- surfaces: ["*"],
89
- permission: {
90
- require: "any",
91
- permissions: ["workspace.settings.view", "workspace.settings.update"]
92
- },
93
- input: workspaceSlugParamsValidator,
94
- output: null,
95
- extensions: {
96
- assistant: {
97
- description: "Read the active workspace profile.",
98
- output: workspaceResource.operations.view.output,
99
- transformResult: (result) => workspaceAssistantSummary(result.value)
100
- }
101
- },
102
- idempotency: "none",
103
- async run(workspaceService, input, context) {
104
- return returnJsonApiData(await workspaceService.getWorkspaceForAuthenticatedUser(
19
+ /** @param {{ workspaceService: import("../types.js").WorkspaceDirectoryService }} dependencies */
20
+ function createWorkspaceDirectoryActions({ workspaceService }) {
21
+ return Object.freeze([
22
+ Object.freeze({
23
+ id: "workspace.workspaces.create",
24
+ version: 1,
25
+ kind: "command",
26
+ channels: ["api", "assistant_tool", "automation", "internal"],
27
+ surfaces: ["*"],
28
+ permission: {
29
+ require: "authenticated"
30
+ },
31
+ input: workspaceResource.operations.create.body,
32
+ output: null,
33
+ extensions: {
34
+ assistant: {
35
+ description: "Create a workspace for the signed-in user.",
36
+ output: workspaceResource.operations.create.output,
37
+ transformResult: (result) => workspaceAssistantSummary(result.value)
38
+ }
39
+ },
40
+ execute: (input, context) => workspaceService.createWorkspaceForAuthenticatedUser(
105
41
  resolveActionUser(context, input),
106
- input.workspaceSlug,
107
- {
108
- request: resolveRequest(context),
109
- context
42
+ input,
43
+ { context }
44
+ )
45
+ }),
46
+ Object.freeze({
47
+ id: "workspace.workspaces.list",
48
+ version: 1,
49
+ kind: "query",
50
+ channels: ["api", "automation", "internal"],
51
+ surfaces: ["*"],
52
+ permission: {
53
+ require: "authenticated"
54
+ },
55
+ input: emptyInputValidator,
56
+ output: null,
57
+ extensions: {
58
+ assistant: {
59
+ description: "List workspaces accessible to the signed-in user.",
60
+ output: workspaceResource.operations.list.output,
61
+ transformResult: (result) => ({
62
+ items: result.value.items.map(({ id, slug, name, avatarUrl, roleSid, isAccessible }) =>
63
+ ({ id, slug, name, avatarUrl, roleSid, isAccessible })),
64
+ nextCursor: result.value.nextCursor
65
+ })
110
66
  }
111
- ));
112
- }
113
- },
114
- {
115
- id: "workspace.workspaces.update",
116
- version: 1,
117
- kind: "command",
118
- channels: ["api", "assistant_tool", "automation", "internal"],
119
- surfaces: ["*"],
120
- permission: {
121
- require: "all",
122
- permissions: ["workspace.settings.update"]
123
- },
124
- input: workspaceUpdateInputValidator,
125
- output: null,
126
- extensions: {
127
- assistant: {
128
- description: "Update the active workspace name or avatar URL.",
129
- output: workspaceResource.operations.patch.output,
130
- transformResult: (result) => workspaceAssistantSummary(result.value)
131
- }
132
- },
133
- idempotency: "optional",
134
- async run(workspaceService, input, context) {
135
- const { workspaceSlug, ...patch } = input;
136
- return returnJsonApiData(await workspaceService.updateWorkspaceForAuthenticatedUser(
67
+ },
68
+ execute: (input, context) => workspaceService.listWorkspacesForAuthenticatedUser(
137
69
  resolveActionUser(context, input),
138
- workspaceSlug,
139
- patch,
140
- {
141
- request: resolveRequest(context),
142
- context
70
+ { context }
71
+ )
72
+ }),
73
+ Object.freeze({
74
+ id: "workspace.workspaces.read",
75
+ version: 1,
76
+ kind: "query",
77
+ channels: ["api", "automation", "internal"],
78
+ surfaces: ["*"],
79
+ permission: {
80
+ require: "any",
81
+ permissions: ["workspace.settings.view", "workspace.settings.update"]
82
+ },
83
+ input: workspaceSlugParamsValidator,
84
+ output: null,
85
+ extensions: {
86
+ assistant: {
87
+ description: "Read the active workspace profile.",
88
+ output: workspaceResource.operations.view.output,
89
+ transformResult: (result) => workspaceAssistantSummary(result.value)
143
90
  }
144
- ));
145
- }
146
- }
147
- ]);
148
-
149
- function buildWorkspaceDirectoryActions({ workspaceService } = {}) {
150
- if (!workspaceService) throw new TypeError("buildWorkspaceDirectoryActions requires workspaceService.");
151
- return workspaceDirectoryActionSpecifications.map(({ run, ...definition }) => Object.freeze({
152
- ...definition,
153
- execute(input, context) {
154
- return run(workspaceService, input, context);
155
- }
156
- }));
91
+ },
92
+ execute: (input, context) => workspaceService.getWorkspaceForAuthenticatedUser(
93
+ resolveActionUser(context, input),
94
+ input.workspaceSlug,
95
+ { context }
96
+ )
97
+ }),
98
+ Object.freeze({
99
+ id: "workspace.workspaces.update",
100
+ version: 1,
101
+ kind: "command",
102
+ channels: ["api", "assistant_tool", "automation", "internal"],
103
+ surfaces: ["*"],
104
+ permission: {
105
+ require: "all",
106
+ permissions: ["workspace.settings.update"]
107
+ },
108
+ input: workspaceUpdateInputValidator,
109
+ output: null,
110
+ extensions: {
111
+ assistant: {
112
+ description: "Update the active workspace name or avatar URL.",
113
+ output: workspaceResource.operations.patch.output,
114
+ transformResult: (result) => workspaceAssistantSummary(result.value)
115
+ }
116
+ },
117
+ execute: (input, context) => workspaceService.updateWorkspaceForAuthenticatedUser(
118
+ resolveActionUser(context, input),
119
+ input.workspaceSlug,
120
+ input,
121
+ { context }
122
+ )
123
+ })
124
+ ]);
157
125
  }
158
126
 
159
- export { workspaceDirectoryActionSpecifications, buildWorkspaceDirectoryActions };
127
+ export { createWorkspaceDirectoryActions };
@@ -7,28 +7,16 @@ import {
7
7
  WORKSPACES_COLLECTION_TRANSPORT,
8
8
  WORKSPACES_TRANSPORT
9
9
  } from "../../shared/jsonApiTransports.js";
10
- import { resolveWorkspaceRoutePath } from "../common/support/workspaceRoutePaths.js";
10
+ import { resolveWorkspaceRecordId } from "../common/support/workspaceRecordId.js";
11
+ import { resolveWorkspaceRoutePath } from "../support/workspaceRoutePaths.js";
11
12
  import { workspaceSlugParamsValidator } from "../common/validators/routeParamsValidator.js";
12
13
  import { resolveDefaultWorkspaceRouteSurfaceIdFromAppConfig } from "../support/workspaceActionSurfaces.js";
13
14
 
14
- function resolveWorkspaceRecordId(record = {}, context = {}) {
15
- const workspaceId = record?.workspace?.id ?? record?.id;
16
- if (workspaceId != null && String(workspaceId).trim()) {
17
- return workspaceId;
18
- }
19
-
20
- const workspaceSlug = context?.request?.params?.workspaceSlug;
21
- if (workspaceSlug != null && String(workspaceSlug).trim()) {
22
- return workspaceSlug;
23
- }
24
-
25
- throw new Error("Workspace JSON:API response requires workspace id.");
26
- }
27
-
28
- function registerWorkspaceDirectoryRoutes(router, { config = {}, workspaceSelfCreateEnabled = false } = {}) {
29
- if (!router || typeof router.register !== "function") {
30
- throw new TypeError("registerWorkspaceDirectoryRoutes requires router.register().");
31
- }
15
+ /**
16
+ * @param {import("@jskit-ai/kernel/server/types").RuntimeRouter} router
17
+ * @param {{ config: import("@jskit-ai/kernel/server/types").RuntimeConfig, workspaceSelfCreateEnabled: boolean }} options
18
+ */
19
+ function registerWorkspaceDirectoryRoutes(router, { config, workspaceSelfCreateEnabled }) {
32
20
  const workspaceRouteSurfaceId = resolveDefaultWorkspaceRouteSurfaceIdFromAppConfig(config);
33
21
 
34
22
  if (workspaceSelfCreateEnabled) {
@@ -1,13 +1,5 @@
1
- import { createWorkspacesServerI18n } from "../i18n.js";
2
-
3
- const englishI18n = createWorkspacesServerI18n();
4
-
5
- function english(key, params) {
6
- return englishI18n.t("en", key, params);
7
- }
8
-
9
- function escapeHtml(value = "") {
10
- return String(value ?? "")
1
+ function escapeHtml(value) {
2
+ return String(value)
11
3
  .replaceAll("&", "&")
12
4
  .replaceAll("<", "&lt;")
13
5
  .replaceAll(">", "&gt;")
@@ -15,35 +7,29 @@ function escapeHtml(value = "") {
15
7
  .replaceAll("'", "&#39;");
16
8
  }
17
9
 
18
- // t renders a message in the recipient's language. The markup stays here; each
19
- // translated fragment, parameters included, is escaped before it enters the HTML.
20
- function renderDefaultWorkspaceInviteEmail({
21
- inviteUrl = "",
22
- workspace = {},
23
- inviter = null,
24
- roleSid = "member",
25
- roleLabel = "",
26
- expiresAt = "",
27
- t = english
28
- } = {}) {
10
+ /**
11
+ * The `workspaces.invite-email-template` default. t renders a message in the
12
+ * recipient's language. The markup stays here; each translated fragment,
13
+ * parameters included, is escaped before it enters the HTML.
14
+ * @type {import("../types.js").WorkspaceInviteEmailTemplate}
15
+ */
16
+ function renderDefaultWorkspaceInviteEmail({ inviteUrl, workspace, inviter, roleSid, roleLabel, expiresAt, t }) {
29
17
  const workspaceName =
30
- String(workspace?.name || workspace?.slug || "").trim() || t("jskit.workspaces.inviteEmail.workspaceFallback");
18
+ String(workspace.name || workspace.slug || "").trim() || t("jskit.workspaces.inviteEmail.workspaceFallback");
31
19
  const inviterName = String(inviter?.displayName || inviter?.email || "").trim();
32
- const roleName = (typeof roleLabel === "string" ? roleLabel.trim() : "") || String(roleSid || "member").trim() || "member";
33
- const expiryText = expiresAt ? t("jskit.workspaces.inviteEmail.expiry", { expiresAt }) : "";
20
+ const roleName = roleLabel || roleSid;
21
+ const expiryText = t("jskit.workspaces.inviteEmail.expiry", { expiresAt });
34
22
  const intro = inviterName
35
23
  ? t("jskit.workspaces.inviteEmail.introFromInviter", { inviter: inviterName, workspace: workspaceName, role: roleName })
36
24
  : t("jskit.workspaces.inviteEmail.intro", { workspace: workspaceName, role: roleName });
37
25
  const subject = t("jskit.workspaces.inviteEmail.subject", { workspace: workspaceName });
38
- const text = [intro, expiryText, t("jskit.workspaces.inviteEmail.openLink"), inviteUrl]
39
- .filter(Boolean)
40
- .join("\n\n");
26
+ const text = [intro, expiryText, t("jskit.workspaces.inviteEmail.openLink"), inviteUrl].join("\n\n");
41
27
  const html = [
42
28
  `<p>${escapeHtml(intro)}</p>`,
43
- expiryText ? `<p>${escapeHtml(expiryText)}</p>` : "",
29
+ `<p>${escapeHtml(expiryText)}</p>`,
44
30
  `<p><a href="${escapeHtml(inviteUrl)}">${escapeHtml(t("jskit.workspaces.inviteEmail.accept"))}</a></p>`,
45
31
  `<p>${escapeHtml(inviteUrl)}</p>`
46
- ].filter(Boolean).join("\n");
32
+ ].join("\n");
47
33
 
48
34
  return {
49
35
  subject,
@@ -0,0 +1,25 @@
1
+ /** The delivery outcome of an invitation when no feature provides a mailer. */
2
+ function buildMailerUnconfiguredDelivery() {
3
+ return {
4
+ status: "mailer_unconfigured",
5
+ message: "No workspace invite mailer is configured.",
6
+ messageCode: "workspaces.invite.mailerUnconfigured",
7
+ messageParams: {},
8
+ providerMessageId: ""
9
+ };
10
+ }
11
+
12
+ /**
13
+ * The `workspaces.invite-mailer` default when no feature provides one: invitations
14
+ * are still created, and their delivery reports that no mailer is configured.
15
+ * The members service recognizes it and answers that outcome without
16
+ * rendering the email.
17
+ * @type {import("../types.js").WorkspaceInviteMailer}
18
+ */
19
+ const unconfiguredWorkspaceInviteMailer = Object.freeze({
20
+ async sendWorkspaceInvite() {
21
+ return buildMailerUnconfiguredDelivery();
22
+ }
23
+ });
24
+
25
+ export { buildMailerUnconfiguredDelivery, unconfiguredWorkspaceInviteMailer };
@@ -1,14 +1,17 @@
1
- function normalizeInvitePathTemplate(value = "") {
1
+ const DEFAULT_INVITE_PATH = "/invite/:token";
2
+
3
+ // The configured invite path as an absolute path template.
4
+ function normalizeInvitePathTemplate(value) {
2
5
  const raw = String(value || "").trim();
3
6
  if (!raw) {
4
- return "/invite/:token";
7
+ return DEFAULT_INVITE_PATH;
5
8
  }
6
9
  return raw.startsWith("/") ? raw : `/${raw}`;
7
10
  }
8
11
 
9
- function buildInvitePath(token, pathTemplate = "/invite/:token") {
12
+ // The invite path for a token: the template's :token or [token] replaced, else the token appended.
13
+ function buildInvitePath(token, template) {
10
14
  const encodedToken = encodeURIComponent(String(token || "").trim());
11
- const template = normalizeInvitePathTemplate(pathTemplate);
12
15
  if (template.includes(":token")) {
13
16
  return template.replaceAll(":token", encodedToken);
14
17
  }
@@ -18,7 +21,8 @@ function buildInvitePath(token, pathTemplate = "/invite/:token") {
18
21
  return `${template.replace(/\/$/, "")}/${encodedToken}`;
19
22
  }
20
23
 
21
- function normalizeBaseUrl(value = "") {
24
+ // An http(s) base URL without its trailing slash, or "" for anything else.
25
+ function normalizeBaseUrl(value) {
22
26
  const raw = String(value || "").trim();
23
27
  if (!raw) {
24
28
  return "";
@@ -34,23 +38,23 @@ function normalizeBaseUrl(value = "") {
34
38
  }
35
39
  }
36
40
 
37
- function createWorkspaceInviteUrlBuilder({
38
- appConfig = {},
39
- env = {}
40
- } = {}) {
41
- const inviteConfig = appConfig?.workspaceInvitations && typeof appConfig.workspaceInvitations === "object"
41
+ /**
42
+ * The invitation URL builder: `workspaceInvitations.invitePath` (default
43
+ * /invite/:token) under `workspaceInvitations.inviteBaseUrl`, else the app's
44
+ * `appPublicUrl`, else APP_PUBLIC_URL; a relative path when none is set.
45
+ * @param {object} sources
46
+ * @param {import("@jskit-ai/kernel/server/types").RuntimeConfig} sources.appConfig
47
+ * @param {import("@jskit-ai/kernel/server/types").RuntimeEnv} sources.env
48
+ */
49
+ function createWorkspaceInviteUrlBuilder({ appConfig, env }) {
50
+ const inviteConfig = appConfig.workspaceInvitations && typeof appConfig.workspaceInvitations === "object"
42
51
  ? appConfig.workspaceInvitations
43
52
  : {};
44
- const baseUrl = normalizeBaseUrl(
45
- inviteConfig.inviteBaseUrl ||
46
- inviteConfig.baseUrl ||
47
- appConfig?.appPublicUrl ||
48
- env.APP_PUBLIC_URL ||
49
- ""
50
- );
51
- const pathTemplate = normalizeInvitePathTemplate(inviteConfig.invitePath || inviteConfig.path || "/invite/:token");
53
+ const baseUrl = normalizeBaseUrl(inviteConfig.inviteBaseUrl || appConfig.appPublicUrl || env.APP_PUBLIC_URL);
54
+ const pathTemplate = normalizeInvitePathTemplate(inviteConfig.invitePath);
52
55
 
53
- return function buildWorkspaceInviteUrl({ token } = {}) {
56
+ /** @param {{ token: string, invite?: object, workspace?: object }} invite */
57
+ return function buildWorkspaceInviteUrl({ token }) {
54
58
  const invitePath = buildInvitePath(token, pathTemplate);
55
59
  if (!baseUrl) {
56
60
  return invitePath;
@@ -59,7 +63,4 @@ function createWorkspaceInviteUrlBuilder({
59
63
  };
60
64
  }
61
65
 
62
- export {
63
- buildInvitePath,
64
- createWorkspaceInviteUrlBuilder
65
- };
66
+ export { createWorkspaceInviteUrlBuilder };