@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,6 +1,17 @@
1
1
  import test from "node:test";
2
2
  import assert from "node:assert/strict";
3
- import { createService } from "../src/server/common/services/workspaceContextService.js";
3
+ import { buildWorkspaceName, createService } from "../src/server/common/services/workspaceDirectoryService.js";
4
+ import { createWorkspaceRoleCatalog } from "../src/shared/roles.js";
5
+ import { resolveTenancyProfile } from "../src/shared/tenancyProfile.js";
6
+
7
+ // The service receives what the workspaces feature resolves from its config.
8
+ function resolvedSettings(appConfig) {
9
+ return {
10
+ tenancyProfile: resolveTenancyProfile(appConfig),
11
+ roleCatalog: createWorkspaceRoleCatalog(appConfig),
12
+ personalWorkspaceName: appConfig.personalWorkspaceName ?? buildWorkspaceName
13
+ };
14
+ }
4
15
 
5
16
  function createRoleCatalog() {
6
17
  return {
@@ -30,8 +41,8 @@ function createWorkspaceServiceFixture({
30
41
  personalWorkspaceName = undefined,
31
42
  personalWorkspace = {
32
43
  id: "1",
33
- slug: "tonymobily3",
34
- name: "TonyMobily3",
44
+ slug: "tony3",
45
+ name: "Tony3",
35
46
  ownerUserId: "7",
36
47
  isPersonal: true,
37
48
  avatarUrl: ""
@@ -70,12 +81,12 @@ function createWorkspaceServiceFixture({
70
81
  }
71
82
 
72
83
  const service = createService({
73
- appConfig: {
84
+ ...resolvedSettings({
74
85
  tenancyMode,
75
86
  tenancyPolicy,
76
87
  personalWorkspaceName,
77
88
  roleCatalog: roleCatalog && typeof roleCatalog === "object" ? { ...roleCatalog } : roleCatalog
78
- },
89
+ }),
79
90
  workspacesRepository: {
80
91
  async findBySlug(slug) {
81
92
  const normalizedSlug = String(slug || "").trim().toLowerCase();
@@ -97,8 +108,8 @@ function createWorkspaceServiceFixture({
97
108
  return [
98
109
  {
99
110
  id: "1",
100
- slug: "tonymobily3",
101
- name: "TonyMobily3",
111
+ slug: "tony3",
112
+ name: "Tony3",
102
113
  avatarUrl: "",
103
114
  roleSid: "owner",
104
115
  membershipStatus: "active"
@@ -190,12 +201,12 @@ test("workspaceService.listWorkspacesForUser returns only accessible workspaces"
190
201
  const { service, calls } = createWorkspaceServiceFixture();
191
202
  const workspaces = await service.listWorkspacesForUser({
192
203
  id: "7",
193
- email: "chiaramobily@gmail.com",
204
+ email: "chiara@example.com",
194
205
  displayName: "Chiara"
195
206
  });
196
207
 
197
208
  assert.equal(workspaces.length, 1);
198
- assert.equal(workspaces[0].slug, "tonymobily3");
209
+ assert.equal(workspaces[0].slug, "tony3");
199
210
  assert.equal(workspaces[0].roleSid, "owner");
200
211
  assert.equal(calls.listForUserId, 1);
201
212
  assert.equal(calls.insert, 0);
@@ -209,7 +220,7 @@ test("workspaceService.listWorkspacesForUser does not provision a personal works
209
220
 
210
221
  await service.listWorkspacesForUser({
211
222
  id: "7",
212
- email: "chiaramobily@gmail.com",
223
+ email: "chiara@example.com",
213
224
  displayName: "Chiara"
214
225
  });
215
226
 
@@ -223,7 +234,7 @@ test("workspaceService.listWorkspacesForUser returns all active memberships in p
223
234
  userWorkspaceRows: [
224
235
  {
225
236
  id: "1",
226
- slug: "chiaramobily",
237
+ slug: "chiara",
227
238
  name: "Chiara Personal",
228
239
  avatarUrl: "",
229
240
  roleSid: "owner",
@@ -231,7 +242,7 @@ test("workspaceService.listWorkspacesForUser returns all active memberships in p
231
242
  },
232
243
  {
233
244
  id: "2",
234
- slug: "tonymobily",
245
+ slug: "tony",
235
246
  name: "Tony Workspace",
236
247
  avatarUrl: "",
237
248
  roleSid: "member",
@@ -250,13 +261,13 @@ test("workspaceService.listWorkspacesForUser returns all active memberships in p
250
261
 
251
262
  const workspaces = await service.listWorkspacesForUser({
252
263
  id: "7",
253
- email: "chiaramobily@gmail.com",
264
+ email: "chiara@example.com",
254
265
  displayName: "Chiara"
255
266
  });
256
267
 
257
268
  assert.deepEqual(
258
269
  workspaces.map((workspace) => workspace.slug),
259
- ["chiaramobily", "tonymobily"]
270
+ ["chiara", "tony"]
260
271
  );
261
272
  assert.equal(calls.findPersonalByOwnerUserId, 0);
262
273
  assert.equal(calls.listForUserId, 1);
@@ -270,7 +281,7 @@ test("workspaceService.ensureProvisionedWorkspaceForAuthenticatedUser provisions
270
281
 
271
282
  const workspace = await service.ensureProvisionedWorkspaceForAuthenticatedUser({
272
283
  id: "7",
273
- email: "chiaramobily@gmail.com",
284
+ email: "chiara@example.com",
274
285
  displayName: "Chiara"
275
286
  });
276
287
 
@@ -297,7 +308,7 @@ test("workspaceService.ensureProvisionedWorkspaceForAuthenticatedUser provisions
297
308
 
298
309
  const workspace = await service.ensureProvisionedWorkspaceForAuthenticatedUser({
299
310
  id: "7",
300
- email: "chiaramobily@gmail.com",
311
+ email: "chiara@example.com",
301
312
  displayName: "Chiara"
302
313
  });
303
314
 
@@ -315,7 +326,7 @@ test("workspaceService.ensureProvisionedWorkspaceForAuthenticatedUser reuses the
315
326
 
316
327
  const workspace = await service.ensureProvisionedWorkspaceForAuthenticatedUser({
317
328
  id: "7",
318
- email: "chiaramobily@gmail.com",
329
+ email: "chiara@example.com",
319
330
  displayName: "Chiara"
320
331
  });
321
332
 
@@ -333,7 +344,7 @@ test("workspaceService.ensureProvisionedWorkspaceForAuthenticatedUser is a no-op
333
344
 
334
345
  const result = await service.ensureProvisionedWorkspaceForAuthenticatedUser({
335
346
  id: "7",
336
- email: "chiaramobily@gmail.com",
347
+ email: "chiara@example.com",
337
348
  displayName: "Chiara"
338
349
  });
339
350
 
@@ -351,17 +362,17 @@ test("workspaceService.createWorkspaceForAuthenticatedUser creates non-personal
351
362
  }
352
363
  });
353
364
 
354
- const workspace = await service.createWorkspaceForAuthenticatedUser(
365
+ const workspace = (await service.createWorkspaceForAuthenticatedUser(
355
366
  {
356
367
  id: "7",
357
- email: "chiaramobily@gmail.com",
368
+ email: "chiara@example.com",
358
369
  displayName: "Chiara"
359
370
  },
360
371
  {
361
372
  name: "Operations Team",
362
373
  slug: "ops-team"
363
374
  }
364
- );
375
+ )).value;
365
376
 
366
377
  assert.equal(workspace.slug, "ops-team");
367
378
  assert.equal(calls.insert, 1);
@@ -381,16 +392,16 @@ test("workspaceService.createWorkspaceForAuthenticatedUser generates an omitted
381
392
  }
382
393
  });
383
394
 
384
- const workspace = await service.createWorkspaceForAuthenticatedUser(
395
+ const workspace = (await service.createWorkspaceForAuthenticatedUser(
385
396
  {
386
397
  id: "7",
387
- email: "chiaramobily@gmail.com",
398
+ email: "chiara@example.com",
388
399
  displayName: "Chiara"
389
400
  },
390
401
  {
391
402
  name: "Operations Team"
392
403
  }
393
- );
404
+ )).value;
394
405
 
395
406
  assert.equal(workspace.slug, "operations-team");
396
407
  assert.equal(calls.insert, 1);
@@ -411,7 +422,7 @@ test("workspaceService.createWorkspaceForAuthenticatedUser rejects creation when
411
422
  service.createWorkspaceForAuthenticatedUser(
412
423
  {
413
424
  id: "7",
414
- email: "chiaramobily@gmail.com",
425
+ email: "chiara@example.com",
415
426
  displayName: "Chiara"
416
427
  },
417
428
  {
@@ -433,10 +444,10 @@ test("workspaceService.resolveWorkspaceContextForUserBySlug returns workspace-no
433
444
  service.resolveWorkspaceContextForUserBySlug(
434
445
  {
435
446
  id: "7",
436
- email: "chiaramobily@gmail.com",
447
+ email: "chiara@example.com",
437
448
  displayName: "Chiara"
438
449
  },
439
- "tonymobily3"
450
+ "tony3"
440
451
  ),
441
452
  /Workspace not found/
442
453
  );
@@ -468,7 +479,7 @@ test("workspaceService.resolveWorkspaceContextForUserBySlug allows personal tena
468
479
  const context = await service.resolveWorkspaceContextForUserBySlug(
469
480
  {
470
481
  id: "7",
471
- email: "chiaramobily@gmail.com",
482
+ email: "chiara@example.com",
472
483
  displayName: "Chiara"
473
484
  },
474
485
  "team-alpha"
@@ -484,19 +495,19 @@ test("workspaceService.resolveWorkspaceContextForUserBySlug grants owner access
484
495
  let membershipRecord = null;
485
496
 
486
497
  const service = createService({
487
- appConfig: {
498
+ ...resolvedSettings({
488
499
  tenancyMode: "personal",
489
500
  roleCatalog: createRoleCatalog()
490
- },
501
+ }),
491
502
  workspacesRepository: {
492
503
  async findBySlug(slug) {
493
- if (String(slug) !== "tonymobily") {
504
+ if (String(slug) !== "tony") {
494
505
  return null;
495
506
  }
496
507
  return {
497
508
  id: "1",
498
- slug: "tonymobily",
499
- name: "TonyMobily",
509
+ slug: "tony",
510
+ name: "Tony",
500
511
  ownerUserId: "7",
501
512
  isPersonal: true,
502
513
  avatarUrl: ""
@@ -539,10 +550,10 @@ test("workspaceService.resolveWorkspaceContextForUserBySlug grants owner access
539
550
  const context = await service.resolveWorkspaceContextForUserBySlug(
540
551
  {
541
552
  id: "7",
542
- email: "chiaramobily@gmail.com",
553
+ email: "chiara@example.com",
543
554
  displayName: "Chiara"
544
555
  },
545
- "tonymobily"
556
+ "tony"
546
557
  );
547
558
 
548
559
  assert.equal(ensuredMembershipCount, 1);
@@ -552,16 +563,16 @@ test("workspaceService.resolveWorkspaceContextForUserBySlug grants owner access
552
563
 
553
564
  test("workspaceService.resolveWorkspaceContextForUserBySlug can resolve an existing workspace without membership", async () => {
554
565
  const service = createService({
555
- appConfig: {
566
+ ...resolvedSettings({
556
567
  tenancyMode: "personal",
557
568
  roleCatalog: createRoleCatalog()
558
- },
569
+ }),
559
570
  workspacesRepository: {
560
571
  async findBySlug() {
561
572
  return {
562
573
  id: "42",
563
574
  slug: "dog-and-groom",
564
- name: "Dog And Groom",
575
+ name: "Harbor Grooming",
565
576
  ownerUserId: "99",
566
577
  isPersonal: false,
567
578
  avatarUrl: ""
@@ -630,10 +641,10 @@ test("workspaceService.resolveWorkspaceContextForUserBySlug resolves permissions
630
641
  const context = await service.resolveWorkspaceContextForUserBySlug(
631
642
  {
632
643
  id: "7",
633
- email: "chiaramobily@gmail.com",
644
+ email: "chiara@example.com",
634
645
  displayName: "Chiara"
635
646
  },
636
- "tonymobily3"
647
+ "tony3"
637
648
  );
638
649
 
639
650
  assert.deepEqual(context.permissions, ["workspace.settings.update"]);
@@ -653,14 +664,14 @@ test("workspaceService.getWorkspaceForAuthenticatedUser resolves workspace from
653
664
  ]
654
665
  });
655
666
 
656
- const workspace = await service.getWorkspaceForAuthenticatedUser(
667
+ const workspace = (await service.getWorkspaceForAuthenticatedUser(
657
668
  {
658
669
  id: "7",
659
- email: "chiaramobily@gmail.com",
670
+ email: "chiara@example.com",
660
671
  displayName: "Chiara"
661
672
  },
662
673
  "team-alpha"
663
- );
674
+ )).value;
664
675
 
665
676
  assert.equal(workspace.slug, "team-alpha");
666
677
  assert.equal(workspace.name, "Team Alpha");
@@ -669,18 +680,18 @@ test("workspaceService.getWorkspaceForAuthenticatedUser resolves workspace from
669
680
  test("workspaceService.updateWorkspaceForAuthenticatedUser updates workspace profile fields", async () => {
670
681
  const { service, calls } = createWorkspaceServiceFixture();
671
682
 
672
- const workspace = await service.updateWorkspaceForAuthenticatedUser(
683
+ const workspace = (await service.updateWorkspaceForAuthenticatedUser(
673
684
  {
674
685
  id: "7",
675
- email: "chiaramobily@gmail.com",
686
+ email: "chiara@example.com",
676
687
  displayName: "Chiara"
677
688
  },
678
- "tonymobily3",
689
+ "tony3",
679
690
  {
680
691
  name: "Updated Workspace",
681
692
  avatarUrl: "https://example.com/acme.png"
682
693
  }
683
- );
694
+ )).value;
684
695
 
685
696
  assert.equal(calls.updateById, 1);
686
697
  assert.equal(workspace.name, "Updated Workspace");
@@ -1,9 +1,16 @@
1
1
  import assert from "node:assert/strict";
2
2
  import test from "node:test";
3
- import { workspaceDirectoryActionSpecifications } from "../src/server/workspaceDirectory/workspaceDirectoryActions.js";
4
- import { workspacePendingInvitationsActionSpecifications } from "../src/server/workspacePendingInvitations/workspacePendingInvitationsActions.js";
5
- import { workspaceMembersActionSpecifications } from "../src/server/workspaceMembers/workspaceMembersActions.js";
6
- import { workspaceSettingsActionSpecifications } from "../src/server/workspaceSettings/workspaceSettingsActions.js";
3
+ import { returnJsonApiData } from "@jskit-ai/http-runtime/shared";
4
+ import { createWorkspaceDirectoryActions } from "../src/server/workspaceDirectory/workspaceDirectoryActions.js";
5
+ import { createWorkspacePendingInvitationsActions } from "../src/server/workspacePendingInvitations/workspacePendingInvitationsActions.js";
6
+ import { createWorkspaceMembersActions } from "../src/server/workspaceMembers/workspaceMembersActions.js";
7
+ import { createWorkspaceSettingsActions } from "../src/server/workspaceSettings/workspaceSettingsActions.js";
8
+
9
+ // The definitions only; no test here executes an action.
10
+ const workspaceDirectoryActionSpecifications = createWorkspaceDirectoryActions({ workspaceService: {} });
11
+ const workspacePendingInvitationsActionSpecifications = createWorkspacePendingInvitationsActions({ workspacePendingInvitationsService: {} });
12
+ const workspaceMembersActionSpecifications = createWorkspaceMembersActions({ workspaceMembersService: {} });
13
+ const workspaceSettingsActionSpecifications = createWorkspaceSettingsActions({ workspaceSettingsService: {} });
7
14
 
8
15
  test("workspace settings action specifications stay explicit", () => {
9
16
  assert.deepEqual(
@@ -64,7 +71,7 @@ test("workspace mutation actions declare their domain and bootstrap changes expl
64
71
  );
65
72
  const events = await Promise.all(settingsAction.events.map((builder) => builder({
66
73
  input: { workspaceSlug: "acme", invitesEnabled: true },
67
- result: { value: { invitesEnabled: true } },
74
+ result: returnJsonApiData({ invitesEnabled: true }),
68
75
  context
69
76
  })));
70
77
 
@@ -86,7 +93,7 @@ test("workspace invite actions carry recipient lookup as delivery policy, not se
86
93
  };
87
94
  const events = await Promise.all(action.events.map((builder) => builder({
88
95
  input: { workspaceSlug: "acme", email: "reader@example.test", roleSid: "member" },
89
- result: { value: { createdInviteId: 91 } },
96
+ result: returnJsonApiData({ createdInviteId: 91 }),
90
97
  context
91
98
  })));
92
99
  assert.equal(events[0].operation, "created");
@@ -116,7 +123,7 @@ test("accepting an invitation declares directory/member refresh while refusal do
116
123
  async function emittedNames(decision) {
117
124
  const built = await Promise.all(action.events.map((builder) => builder({
118
125
  input: { decision, token: "opaque" },
119
- result: { value: { workspaceId: 11 } },
126
+ result: returnJsonApiData({ workspaceId: 11 }),
120
127
  context: { actor: { id: 7 } }
121
128
  })));
122
129
  return built.filter(Boolean).map((event) => event.realtime.event);
@@ -115,6 +115,8 @@ function createWorkspaceSettingsApiStub(rowOverrides = {}) {
115
115
  test("workspaceSettingsRepository.findByWorkspaceId returns the canonical workspace-settings row", async () => {
116
116
  const { api, DEFAULT_WORKSPACE_THEME, STUB_CREATED_AT } = createWorkspaceSettingsApiStub();
117
117
  const repository = createRepository({
118
+ defaultInvitesEnabled: true,
119
+ defaultColorSettings: {},
118
120
  api
119
121
  });
120
122
 
@@ -139,6 +141,8 @@ test("workspaceSettingsRepository.findByWorkspaceId returns the canonical worksp
139
141
  test("workspaceSettingsRepository.updateSettingsByWorkspaceId updates invitesEnabled only", async () => {
140
142
  const { api, state } = createWorkspaceSettingsApiStub();
141
143
  const repository = createRepository({
144
+ defaultInvitesEnabled: true,
145
+ defaultColorSettings: {},
142
146
  api
143
147
  });
144
148
 
@@ -154,6 +158,8 @@ test("workspaceSettingsRepository.ensureForWorkspaceId delegates defaults to the
154
158
  const { api, state, DEFAULT_WORKSPACE_THEME } = createWorkspaceSettingsApiStub();
155
159
  state.row = null;
156
160
  const repository = createRepository({
161
+ defaultInvitesEnabled: true,
162
+ defaultColorSettings: {},
157
163
  api
158
164
  });
159
165
 
@@ -185,6 +191,7 @@ test("workspaceSettingsRepository.ensureForWorkspaceId starts new workspaces fro
185
191
  const { api, state, DEFAULT_WORKSPACE_THEME } = createWorkspaceSettingsApiStub();
186
192
  state.row = null;
187
193
  const repository = createRepository({
194
+ defaultInvitesEnabled: true,
188
195
  api,
189
196
  defaultColorSettings: {
190
197
  lightPrimaryColor: "#1C1C1C",
@@ -203,6 +210,8 @@ test("workspaceSettingsRepository.ensureForWorkspaceId starts new workspaces fro
203
210
  test("workspaceSettingsRepository.updateSettingsByWorkspaceId updates workspace settings fields", async () => {
204
211
  const { api, state } = createWorkspaceSettingsApiStub();
205
212
  const repository = createRepository({
213
+ defaultInvitesEnabled: true,
214
+ defaultColorSettings: {},
206
215
  api
207
216
  });
208
217
 
@@ -218,6 +227,8 @@ test("workspaceSettingsRepository can be constructed without validating app conf
218
227
  const { api } = createWorkspaceSettingsApiStub();
219
228
 
220
229
  const repository = createRepository({
230
+ defaultInvitesEnabled: true,
231
+ defaultColorSettings: {},
221
232
  api
222
233
  });
223
234
 
@@ -2,6 +2,7 @@ import assert from "node:assert/strict";
2
2
  import test from "node:test";
3
3
  import { resolveWorkspaceThemePalettes } from "@jskit-ai/workspaces-core/shared/settings";
4
4
  import { createService } from "../src/server/workspaceSettings/workspaceSettingsService.js";
5
+ import { createWorkspaceRoleCatalog } from "../src/shared/roles.js";
5
6
 
6
7
  function authorizedOptions(permissions = []) {
7
8
  return {
@@ -22,8 +23,8 @@ function createFixture({ workspaceInvitationsEnabled = true } = {}) {
22
23
  settingsPatch: null,
23
24
  workspace: {
24
25
  id: 7,
25
- slug: "tonymobily3",
26
- name: "TonyMobily3",
26
+ slug: "tony3",
27
+ name: "Tony3",
27
28
  ownerUserId: 9
28
29
  },
29
30
  settings: {
@@ -55,7 +56,8 @@ function createFixture({ workspaceInvitationsEnabled = true } = {}) {
55
56
  };
56
57
  return state.settings;
57
58
  }
58
- }
59
+ },
60
+ roleCatalog: createWorkspaceRoleCatalog()
59
61
  });
60
62
 
61
63
  return { service, state };
@@ -64,10 +66,10 @@ function createFixture({ workspaceInvitationsEnabled = true } = {}) {
64
66
  test("workspaceSettingsService.getWorkspaceSettings returns the stored invitesEnabled flag", async () => {
65
67
  const { service, state } = createFixture();
66
68
 
67
- const response = await service.getWorkspaceSettings(
69
+ const response = (await service.getWorkspaceSettings(
68
70
  state.workspace,
69
71
  authorizedOptions(["workspace.settings.view"])
70
- );
72
+ )).value;
71
73
 
72
74
  assert.deepEqual(response.settings, {
73
75
  lightPrimaryColor: "#0F6B54",
@@ -87,13 +89,13 @@ test("workspaceSettingsService.getWorkspaceSettings returns the stored invitesEn
87
89
  test("workspaceSettingsService.updateWorkspaceSettings writes editable fields through workspaceSettingsRepository only", async () => {
88
90
  const { service, state } = createFixture();
89
91
 
90
- const response = await service.updateWorkspaceSettings(
92
+ const response = (await service.updateWorkspaceSettings(
91
93
  state.workspace,
92
94
  {
93
95
  invitesEnabled: false
94
96
  },
95
97
  authorizedOptions(["workspace.settings.update"])
96
- );
98
+ )).value;
97
99
 
98
100
  assert.deepEqual(state.settingsPatch, {
99
101
  invitesEnabled: false
@@ -118,10 +120,10 @@ test("workspaceSettingsService disables invite settings in output when app polic
118
120
  workspaceInvitationsEnabled: false
119
121
  });
120
122
 
121
- const response = await service.getWorkspaceSettings(
123
+ const response = (await service.getWorkspaceSettings(
122
124
  state.workspace,
123
125
  authorizedOptions(["workspace.settings.view"])
124
- );
126
+ )).value;
125
127
 
126
128
  assert.deepEqual(response.settings, {
127
129
  lightPrimaryColor: "#0F6B54",
@@ -150,8 +150,8 @@ test("workspacesRepository.findById reads a canonical workspace row through json
150
150
  rowsById: new Map([
151
151
  ["7", {
152
152
  id: "7",
153
- slug: "tonymobily3",
154
- name: "TonyMobily3",
153
+ slug: "tony3",
154
+ name: "Tony3",
155
155
  ownerUserId: "9",
156
156
  isPersonal: true,
157
157
  avatarUrl: "",
@@ -167,8 +167,8 @@ test("workspacesRepository.findById reads a canonical workspace row through json
167
167
 
168
168
  assert.deepEqual(workspace, {
169
169
  id: "7",
170
- slug: "tonymobily3",
171
- name: "TonyMobily3",
170
+ slug: "tony3",
171
+ name: "Tony3",
172
172
  ownerUserId: "9",
173
173
  isPersonal: true,
174
174
  avatarUrl: "",
@@ -220,16 +220,16 @@ test("workspacesRepository.insert writes canonical fields through json-rest-api"
220
220
  const repository = createRepository({ api });
221
221
 
222
222
  const inserted = await repository.insert({
223
- slug: "TonyMobily3",
224
- name: "TonyMobily3",
223
+ slug: "Tony3",
224
+ name: "Tony3",
225
225
  ownerUserId: "9",
226
226
  avatarUrl: "",
227
227
  isPersonal: false
228
228
  });
229
229
 
230
230
  assert.equal(state.postPayload.data.owner, "9");
231
- assert.equal(state.postPayload.data.slug, "TonyMobily3");
232
- assert.equal(state.postPayload.data.name, "TonyMobily3");
231
+ assert.equal(state.postPayload.data.slug, "Tony3");
232
+ assert.equal(state.postPayload.data.name, "Tony3");
233
233
  assert.equal(state.postPayload.data.isPersonal, false);
234
234
  assert.equal(state.postPayload.data.avatarUrl, "");
235
235
  assert.equal(typeof state.postPayload.data.createdAt, "string");
@@ -271,8 +271,8 @@ test("workspacesRepository.updateById patches canonical fields and updatedAt", a
271
271
  rowsById: new Map([
272
272
  ["7", {
273
273
  id: "7",
274
- slug: "tonymobily3",
275
- name: "TonyMobily3",
274
+ slug: "tony3",
275
+ name: "Tony3",
276
276
  ownerUserId: "9",
277
277
  isPersonal: false,
278
278
  avatarUrl: "",
@@ -285,11 +285,11 @@ test("workspacesRepository.updateById patches canonical fields and updatedAt", a
285
285
  const repository = createRepository({ api });
286
286
 
287
287
  await repository.updateById("7", {
288
- name: "TonyMobily 4"
288
+ name: "Tony 4"
289
289
  });
290
290
 
291
291
  assert.equal(state.patchPayload.id, "7");
292
- assert.equal(state.patchPayload.data.name, "TonyMobily 4");
292
+ assert.equal(state.patchPayload.data.name, "Tony 4");
293
293
  assert.equal(typeof state.patchPayload.data.updatedAt, "string");
294
294
  });
295
295
 
@@ -302,8 +302,8 @@ test("workspacesRepository.listForUserId keeps membership fields outside the can
302
302
  status: "active",
303
303
  workspace: {
304
304
  id: "7",
305
- slug: "tonymobily3",
306
- name: "TonyMobily3",
305
+ slug: "tony3",
306
+ name: "Tony3",
307
307
  ownerUserId: "9",
308
308
  isPersonal: true,
309
309
  avatarUrl: "",
@@ -353,8 +353,8 @@ test("workspacesRepository.listForUserId keeps membership fields outside the can
353
353
  assert.deepEqual(workspaces, [
354
354
  {
355
355
  id: "7",
356
- slug: "tonymobily3",
357
- name: "TonyMobily3",
356
+ slug: "tony3",
357
+ name: "Tony3",
358
358
  ownerUserId: "9",
359
359
  isPersonal: true,
360
360
  avatarUrl: "",
@@ -1,10 +1,10 @@
1
1
  import assert from "node:assert/strict";
2
2
  import test from "node:test";
3
3
  import { resolveTenancyProfile } from "../src/shared/tenancyProfile.js";
4
- import { registerWorkspaceDirectoryRoutes } from "../src/server/workspaceDirectory/bootWorkspaceDirectoryRoutes.js";
5
- import { registerWorkspaceMembersRoutes } from "../src/server/workspaceMembers/bootWorkspaceMembers.js";
6
- import { registerWorkspacePendingInvitationsRoutes } from "../src/server/workspacePendingInvitations/bootWorkspacePendingInvitations.js";
7
- import { registerWorkspaceSettingsRoutes } from "../src/server/workspaceSettings/bootWorkspaceSettings.js";
4
+ import { registerWorkspaceDirectoryRoutes } from "../src/server/workspaceDirectory/workspaceDirectoryRoutes.js";
5
+ import { registerWorkspaceMembersRoutes } from "../src/server/workspaceMembers/workspaceMembersRoutes.js";
6
+ import { registerWorkspacePendingInvitationsRoutes } from "../src/server/workspacePendingInvitations/workspacePendingInvitationsRoutes.js";
7
+ import { registerWorkspaceSettingsRoutes } from "../src/server/workspaceSettings/workspaceSettingsRoutes.js";
8
8
 
9
9
  function createReplyDouble() {
10
10
  return {
@@ -1,43 +0,0 @@
1
- import {
2
- normalizeDbRecordId,
3
- retryDeadlocks,
4
- toIsoString
5
- } from "@jskit-ai/database-runtime/shared";
6
- import { isDuplicateEntryError } from "@jskit-ai/database-runtime/shared/duplicateEntry";
7
- import { normalizeLowerText, normalizeRecordId, normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
8
-
9
- function uniqueSorted(values) {
10
- return [...new Set(values)].sort((left, right) => String(left).localeCompare(String(right)));
11
- }
12
-
13
- function parseJson(value, fallback = {}) {
14
- if (value == null) {
15
- return fallback;
16
- }
17
- if (typeof value === "object") {
18
- return value;
19
- }
20
- try {
21
- return JSON.parse(String(value));
22
- } catch {
23
- return fallback;
24
- }
25
- }
26
-
27
- function toDbJson(value, fallback = {}) {
28
- const source = value && typeof value === "object" ? value : fallback;
29
- return JSON.stringify(source);
30
- }
31
-
32
- export {
33
- toIsoString,
34
- retryDeadlocks,
35
- isDuplicateEntryError,
36
- normalizeText,
37
- normalizeLowerText,
38
- normalizeRecordId,
39
- normalizeDbRecordId,
40
- uniqueSorted,
41
- parseJson,
42
- toDbJson
43
- };
@@ -1 +0,0 @@
1
- export { deepFreeze } from "@jskit-ai/kernel/shared/support/deepFreeze";
@@ -1,9 +0,0 @@
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 };