@jskit-ai/workspaces-core 0.1.30 → 0.1.32

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 (61) hide show
  1. package/package.descriptor.mjs +11 -22
  2. package/package.json +11 -9
  3. package/src/server/WorkspacesCoreServiceProvider.js +22 -2
  4. package/src/server/common/repositories/workspaceInvitesRepository.js +233 -78
  5. package/src/server/common/repositories/workspaceMembershipsRepository.js +177 -86
  6. package/src/server/common/repositories/workspacesRepository.js +179 -86
  7. package/src/server/common/services/workspaceContextService.js +26 -24
  8. package/src/server/common/validators/routeParamsValidator.js +36 -53
  9. package/src/server/registerWorkspaceCore.js +6 -7
  10. package/src/server/registerWorkspaceRepositories.js +7 -3
  11. package/src/server/support/workspaceServerScopeSupport.js +1 -1
  12. package/src/server/workspaceBootstrapContributor.js +5 -14
  13. package/src/server/workspaceDirectory/bootWorkspaceDirectoryRoutes.js +54 -27
  14. package/src/server/workspaceDirectory/workspaceDirectoryActions.js +30 -24
  15. package/src/server/workspaceMembers/bootWorkspaceMembers.js +70 -32
  16. package/src/server/workspaceMembers/workspaceMembersActions.js +61 -27
  17. package/src/server/workspaceMembers/workspaceMembersService.js +43 -7
  18. package/src/server/workspacePendingInvitations/bootWorkspacePendingInvitations.js +28 -13
  19. package/src/server/workspacePendingInvitations/workspacePendingInvitationsActions.js +13 -15
  20. package/src/server/workspacePendingInvitations/workspacePendingInvitationsService.js +33 -10
  21. package/src/server/workspaceSettings/bootWorkspaceSettings.js +32 -13
  22. package/src/server/workspaceSettings/registerWorkspaceSettings.js +5 -1
  23. package/src/server/workspaceSettings/workspaceSettingsActions.js +18 -12
  24. package/src/server/workspaceSettings/workspaceSettingsRepository.js +104 -91
  25. package/src/server/workspaceSettings/workspaceSettingsService.js +5 -6
  26. package/src/shared/jsonApiTransports.js +79 -0
  27. package/src/shared/resources/workspaceInvitesResource.js +158 -0
  28. package/src/shared/resources/workspaceMembersResource.js +176 -311
  29. package/src/shared/resources/workspaceMembershipsResource.js +96 -0
  30. package/src/shared/resources/workspacePendingInvitationsResource.js +25 -72
  31. package/src/shared/resources/workspaceResource.js +113 -144
  32. package/src/shared/resources/workspaceRoleCatalogSchema.js +31 -0
  33. package/src/shared/resources/workspaceSettingsResource.js +276 -148
  34. package/test/repositoryContracts.test.js +16 -4
  35. package/test/resourcesCanonical.test.js +39 -16
  36. package/test/routeParamsValidator.test.js +37 -19
  37. package/test/usersRouteResources.test.js +27 -17
  38. package/test/workspaceActionContextContributor.test.js +1 -1
  39. package/test/workspaceInternalCrudResources.test.js +98 -0
  40. package/test/workspaceInvitesRepository.test.js +196 -148
  41. package/test/workspaceMembersResource.test.js +35 -0
  42. package/test/workspaceMembershipsRepository.test.js +155 -115
  43. package/test/workspacePendingInvitationsResource.test.js +18 -23
  44. package/test/workspacePendingInvitationsService.test.js +2 -1
  45. package/test/workspaceServerScopeSupport.test.js +21 -3
  46. package/test/workspaceSettingsActions.test.js +5 -7
  47. package/test/workspaceSettingsInternalResource.test.js +8 -0
  48. package/test/workspaceSettingsRepository.test.js +158 -123
  49. package/test/workspaceSettingsResource.test.js +51 -62
  50. package/test/workspaceSettingsService.test.js +0 -1
  51. package/test/workspacesRepository.test.js +318 -174
  52. package/test/workspacesRouteRequestInputValidator.test.js +25 -11
  53. package/src/server/common/resources/workspaceInvitesResource.js +0 -207
  54. package/src/server/common/resources/workspaceMembershipsResource.js +0 -154
  55. package/src/server/common/resources/workspacesResource.js +0 -170
  56. package/src/server/common/validators/authenticatedUserValidator.js +0 -43
  57. package/src/shared/resources/resolveGlobalArrayRegistry.js +0 -6
  58. package/src/shared/resources/workspaceSettingsFields.js +0 -65
  59. package/templates/packages/main/src/shared/resources/workspaceSettingsFields.js +0 -197
  60. package/test/settingsFieldRegistriesSingleton.test.js +0 -14
  61. package/test-support/registerDefaultSettingsFields.js +0 -1
@@ -3,108 +3,146 @@ import test from "node:test";
3
3
  import { toIsoString } from "@jskit-ai/database-runtime/shared";
4
4
  import { createRepository } from "../src/server/common/repositories/workspaceMembershipsRepository.js";
5
5
 
6
- function createKnexStub({
7
- rowById = new Map(),
6
+ function createKnexStub() {
7
+ const knex = Object.assign(() => {
8
+ throw new Error("query execution not expected");
9
+ }, {
10
+ async transaction(work) {
11
+ return work({ trxId: "trx-1" });
12
+ }
13
+ });
14
+
15
+ return knex;
16
+ }
17
+
18
+ function toWorkspaceMembershipResource(row = {}) {
19
+ return {
20
+ type: "workspaceMemberships",
21
+ id: String(row.id || ""),
22
+ attributes: {
23
+ roleSid: row.roleSid,
24
+ status: row.status,
25
+ createdAt: row.createdAt,
26
+ updatedAt: row.updatedAt
27
+ },
28
+ relationships: {
29
+ workspace: {
30
+ data: row?.workspace?.id == null ? null : { type: "workspaces", id: String(row.workspace.id) }
31
+ },
32
+ user: {
33
+ data: row?.user?.id == null
34
+ ? null
35
+ : {
36
+ type: "userProfiles",
37
+ id: String(row.user.id)
38
+ }
39
+ }
40
+ }
41
+ };
42
+ }
43
+
44
+ function createWorkspaceMembershipsApiStub({
8
45
  rowByComposite = new Map(),
9
- memberSummaryRows = []
46
+ memberSummaryRows = [],
47
+ rowById = new Map()
10
48
  } = {}) {
11
49
  const state = {
12
- insertPayload: null,
13
- updatePayload: null
50
+ postPayload: null,
51
+ patchPayload: null
14
52
  };
15
53
 
16
- function buildMembershipsQuery(tableName) {
17
- if (tableName === "workspace_memberships as wm") {
18
- return {
19
- join() {
20
- return this;
21
- },
22
- where() {
23
- return this;
54
+ const api = {
55
+ resources: {
56
+ workspaceMemberships: {
57
+ async query({ queryParams }) {
58
+ const filters = queryParams?.filters || {};
59
+ const includeUser = Array.isArray(queryParams?.include) && queryParams.include.includes("user");
60
+
61
+ if (Object.hasOwn(filters, "workspace") && Object.hasOwn(filters, "user")) {
62
+ const row = rowByComposite.get(`${filters.workspace}:${filters.user}`) || null;
63
+ return { data: row ? [toWorkspaceMembershipResource(row)] : [] };
64
+ }
65
+
66
+ if (Object.hasOwn(filters, "workspace") && Object.hasOwn(filters, "status") && includeUser) {
67
+ return {
68
+ data: memberSummaryRows.map((row) => toWorkspaceMembershipResource(row)),
69
+ included: memberSummaryRows
70
+ .filter((row) => row?.user?.id != null)
71
+ .map((row) => ({
72
+ type: "userProfiles",
73
+ id: String(row.user.id),
74
+ attributes: {
75
+ displayName: row.user.displayName,
76
+ email: row.user.email
77
+ }
78
+ }))
79
+ };
80
+ }
81
+
82
+ if (Object.hasOwn(filters, "user") && Object.hasOwn(filters, "status")) {
83
+ const rows = [...rowByComposite.values()].filter((row) => (
84
+ String(row?.user?.id || "") === String(filters.user) &&
85
+ String(row?.status || "") === String(filters.status)
86
+ ));
87
+ return { data: rows.map((row) => toWorkspaceMembershipResource(row)) };
88
+ }
89
+
90
+ return { data: [] };
24
91
  },
25
- orderBy() {
26
- return this;
92
+ async post(payload) {
93
+ assert.equal(payload?.simplified, false);
94
+ const inputRecord = payload?.inputRecord?.data || {};
95
+ state.postPayload = inputRecord;
96
+ const row = rowById.get("1") || {
97
+ id: "1",
98
+ workspace: { id: String(inputRecord.relationships?.workspace?.data?.id || "") },
99
+ user: { id: String(inputRecord.relationships?.user?.data?.id || "") },
100
+ roleSid: String(inputRecord.attributes?.roleSid || ""),
101
+ status: String(inputRecord.attributes?.status || ""),
102
+ createdAt: "2026-03-09 00:26:35.710",
103
+ updatedAt: "2026-03-09 00:26:35.710"
104
+ };
105
+ rowByComposite.set(`${row.workspace.id}:${row.user.id}`, row);
106
+ rowById.set(String(row.id), row);
107
+ return { data: toWorkspaceMembershipResource(row) };
27
108
  },
28
- select() {
29
- return Promise.resolve([...memberSummaryRows]);
30
- }
31
- };
32
- }
33
-
34
- const query = {
35
- criteriaList: [],
36
- select() {
37
- return this;
38
- },
39
- insert(payload) {
40
- state.insertPayload = payload;
41
- const insertedRow = rowById.get("1");
42
- if (insertedRow) {
43
- rowByComposite.set(`${payload.workspace_id}:${payload.user_id}`, insertedRow);
44
- }
45
- return Promise.resolve([1]);
46
- },
47
- where(criteria) {
48
- this.criteriaList.push(criteria);
49
- return this;
50
- },
51
- update(payload) {
52
- state.updatePayload = payload;
53
- const criteria = Object.assign({}, ...this.criteriaList);
54
- const existingRow = rowById.get(String(criteria.id));
55
- if (existingRow) {
56
- const updatedRow = {
57
- ...existingRow,
58
- ...payload
109
+ async patch(payload) {
110
+ assert.equal(payload?.simplified, false);
111
+ const inputRecord = payload?.inputRecord?.data || {};
112
+ state.patchPayload = inputRecord;
113
+ const existing = rowById.get(String(inputRecord.id));
114
+ const updated = {
115
+ ...(existing || {}),
116
+ ...(inputRecord.attributes || {}),
117
+ id: String(inputRecord.id),
118
+ workspace: existing?.workspace || { id: "" },
119
+ user: existing?.user || { id: "" }
59
120
  };
60
- rowById.set(String(criteria.id), updatedRow);
61
- rowByComposite.set(`${updatedRow.workspace_id}:${updatedRow.user_id}`, updatedRow);
121
+ rowById.set(String(updated.id), updated);
122
+ rowByComposite.set(`${updated.workspace.id}:${updated.user.id}`, updated);
123
+ return { data: toWorkspaceMembershipResource(updated) };
62
124
  }
63
- return Promise.resolve(1);
64
- },
65
- first() {
66
- const criteria = Object.assign({}, ...this.criteriaList);
67
- if (Object.hasOwn(criteria, "id")) {
68
- return Promise.resolve(rowById.get(String(criteria.id)) || null);
69
- }
70
- if (Object.hasOwn(criteria, "workspace_id") && Object.hasOwn(criteria, "user_id")) {
71
- return Promise.resolve(
72
- rowByComposite.get(`${criteria.workspace_id}:${criteria.user_id}`) || null
73
- );
74
- }
75
- return Promise.resolve(null);
76
125
  }
77
- };
78
-
79
- return query;
80
- }
81
-
82
- function knex(tableName) {
83
- if (tableName === "workspace_memberships" || tableName === "workspace_memberships as wm") {
84
- return buildMembershipsQuery(tableName);
85
126
  }
86
- throw new Error(`Unexpected table ${tableName}`);
87
- }
88
-
89
- knex.transaction = async (work) => work(knex);
127
+ };
90
128
 
91
- return { knex, state };
129
+ return { api, state };
92
130
  }
93
131
 
94
132
  test("workspaceMembershipsRepository.findByWorkspaceIdAndUserId normalizes canonical membership rows via the internal resource", async () => {
95
133
  const membershipRow = {
96
- id: 11,
97
- workspace_id: 7,
98
- user_id: 9,
99
- role_sid: "owner",
134
+ id: "11",
135
+ workspace: { id: "7" },
136
+ user: { id: "9" },
137
+ roleSid: "owner",
100
138
  status: "active",
101
- created_at: "2026-03-09 00:26:35.710",
102
- updated_at: "2026-03-10 00:26:35.710"
139
+ createdAt: "2026-03-09 00:26:35.710",
140
+ updatedAt: "2026-03-10 00:26:35.710"
103
141
  };
104
- const { knex } = createKnexStub({
142
+ const { api } = createWorkspaceMembershipsApiStub({
105
143
  rowByComposite: new Map([["7:9", membershipRow]])
106
144
  });
107
- const repository = createRepository(knex);
145
+ const repository = createRepository({ api, knex: createKnexStub() });
108
146
 
109
147
  const membership = await repository.findByWorkspaceIdAndUserId("7", "9");
110
148
 
@@ -121,31 +159,31 @@ test("workspaceMembershipsRepository.findByWorkspaceIdAndUserId normalizes canon
121
159
 
122
160
  test("workspaceMembershipsRepository.ensureOwnerMembership upgrades an existing membership through the runtime update path", async () => {
123
161
  const existingRow = {
124
- id: 11,
125
- workspace_id: 7,
126
- user_id: 9,
127
- role_sid: "member",
162
+ id: "11",
163
+ workspace: { id: "7" },
164
+ user: { id: "9" },
165
+ roleSid: "member",
128
166
  status: "pending",
129
- created_at: "2026-03-09 00:26:35.710",
130
- updated_at: "2026-03-09 00:26:35.710"
167
+ createdAt: "2026-03-09 00:26:35.710",
168
+ updatedAt: "2026-03-09 00:26:35.710"
131
169
  };
132
170
  const refreshedRow = {
133
171
  ...existingRow,
134
- role_sid: "owner",
172
+ roleSid: "owner",
135
173
  status: "active",
136
- updated_at: "2026-03-10 00:26:35.710"
174
+ updatedAt: "2026-03-10 00:26:35.710"
137
175
  };
138
- const { knex, state } = createKnexStub({
176
+ const { api, state } = createWorkspaceMembershipsApiStub({
139
177
  rowById: new Map([["11", refreshedRow]]),
140
178
  rowByComposite: new Map([["7:9", existingRow]])
141
179
  });
142
- const repository = createRepository(knex);
180
+ const repository = createRepository({ api, knex: createKnexStub() });
143
181
 
144
182
  const membership = await repository.ensureOwnerMembership("7", "9");
145
183
 
146
- assert.equal(state.updatePayload.role_sid, "owner");
147
- assert.equal(state.updatePayload.status, "active");
148
- assert.equal(typeof state.updatePayload.updated_at, "string");
184
+ assert.equal(state.patchPayload.attributes?.roleSid, "owner");
185
+ assert.equal(state.patchPayload.attributes?.status, "active");
186
+ assert.equal(typeof state.patchPayload.attributes?.updatedAt, "object");
149
187
  assert.deepEqual(membership, {
150
188
  id: "11",
151
189
  workspaceId: "7",
@@ -153,50 +191,52 @@ test("workspaceMembershipsRepository.ensureOwnerMembership upgrades an existing
153
191
  roleSid: "owner",
154
192
  status: "active",
155
193
  createdAt: toIsoString("2026-03-09 00:26:35.710"),
156
- updatedAt: toIsoString(state.updatePayload.updated_at)
194
+ updatedAt: toIsoString(state.patchPayload.attributes.updatedAt)
157
195
  });
158
196
  });
159
197
 
160
198
  test("workspaceMembershipsRepository.upsertMembership creates normalized memberships through the runtime create path", async () => {
161
199
  const createdRow = {
162
- id: 1,
163
- workspace_id: 7,
164
- user_id: 9,
165
- role_sid: "admin",
200
+ id: "1",
201
+ workspace: { id: "7" },
202
+ user: { id: "9" },
203
+ roleSid: "admin",
166
204
  status: "active",
167
- created_at: "2026-03-09 00:26:35.710",
168
- updated_at: "2026-03-09 00:26:35.710"
205
+ createdAt: "2026-03-09 00:26:35.710",
206
+ updatedAt: "2026-03-09 00:26:35.710"
169
207
  };
170
- const { knex, state } = createKnexStub({
208
+ const { api, state } = createWorkspaceMembershipsApiStub({
171
209
  rowById: new Map([["1", createdRow]]),
172
210
  rowByComposite: new Map()
173
211
  });
174
- const repository = createRepository(knex);
212
+ const repository = createRepository({ api, knex: createKnexStub() });
175
213
 
176
214
  await repository.upsertMembership("7", "9", {
177
215
  roleSid: "ADMIN",
178
216
  status: "ACTIVE"
179
217
  });
180
218
 
181
- assert.equal(state.insertPayload.workspace_id, "7");
182
- assert.equal(state.insertPayload.user_id, "9");
183
- assert.equal(state.insertPayload.role_sid, "admin");
184
- assert.equal(state.insertPayload.status, "active");
219
+ assert.equal(state.postPayload.relationships?.workspace?.data?.id, "7");
220
+ assert.equal(state.postPayload.relationships?.user?.data?.id, "9");
221
+ assert.equal(state.postPayload.attributes?.roleSid, "admin");
222
+ assert.equal(state.postPayload.attributes?.status, "active");
185
223
  });
186
224
 
187
225
  test("workspaceMembershipsRepository.listActiveByWorkspaceId keeps summary rows separate from the canonical membership resource", async () => {
188
- const { knex } = createKnexStub({
226
+ const { api } = createWorkspaceMembershipsApiStub({
189
227
  memberSummaryRows: [
190
228
  {
191
- user_id: 9,
192
- role_sid: "owner",
193
- status: "active",
194
- display_name: "Chiara",
195
- email: "CHIARA@example.com"
229
+ user: {
230
+ id: "9",
231
+ displayName: "Chiara",
232
+ email: "CHIARA@example.com"
233
+ },
234
+ roleSid: "owner",
235
+ status: "active"
196
236
  }
197
237
  ]
198
238
  });
199
- const repository = createRepository(knex);
239
+ const repository = createRepository({ api, knex: createKnexStub() });
200
240
 
201
241
  const members = await repository.listActiveByWorkspaceId("7");
202
242
 
@@ -1,38 +1,33 @@
1
1
  import test from "node:test";
2
2
  import assert from "node:assert/strict";
3
- import { encodeInviteTokenHash } from "@jskit-ai/auth-core/shared/inviteTokens";
3
+ import { resolveStructuredSchemaTransportSchema } from "@jskit-ai/kernel/shared/validators";
4
4
  import { workspacePendingInvitationsResource } from "../src/shared/resources/workspacePendingInvitationsResource.js";
5
5
 
6
- test("workspacePendingInvitationsResource output normalizer shapes raw invite rows", () => {
7
- const tokenHash = "a".repeat(64);
8
-
9
- const result = workspacePendingInvitationsResource.operations.list.outputValidator.normalize({
6
+ test("workspacePendingInvitationsResource output schema accepts already-shaped invite payloads", () => {
7
+ const outputSchema = resolveStructuredSchemaTransportSchema(workspacePendingInvitationsResource.operations.list.output, {
8
+ context: "workspacePendingInvitations.list.output",
9
+ defaultMode: "replace"
10
+ });
11
+ const result = {
10
12
  pendingInvites: [
11
13
  {
12
14
  id: "10",
13
15
  workspaceId: "3",
14
16
  workspaceSlug: "tonymobily3",
15
- workspaceName: "",
17
+ workspaceName: "TonyMobily3",
16
18
  workspaceAvatarUrl: "",
17
- roleSid: "Member",
18
- status: "Pending",
19
+ roleSid: "member",
20
+ status: "pending",
19
21
  expiresAt: "2030-01-01T00:00:00.000Z",
20
- tokenHash
22
+ token: "opaque-token"
21
23
  }
22
24
  ]
23
- }).pendingInvites;
25
+ };
24
26
 
25
- assert.deepEqual(result, [
26
- {
27
- id: "10",
28
- workspaceId: "3",
29
- workspaceSlug: "tonymobily3",
30
- workspaceName: "tonymobily3",
31
- workspaceAvatarUrl: "",
32
- roleSid: "member",
33
- status: "pending",
34
- expiresAt: "2030-01-01T00:00:00.000Z",
35
- token: encodeInviteTokenHash(tokenHash)
36
- }
37
- ]);
27
+ assert.equal(outputSchema.type, "object");
28
+ assert.equal(outputSchema.additionalProperties, false);
29
+ assert.equal(outputSchema.properties.pendingInvites.type, "array");
30
+ assert.equal(outputSchema.properties.pendingInvites.items["x-json-rest-schema"]?.castType, "object");
31
+ assert.equal(Array.isArray(outputSchema.properties.pendingInvites.items.allOf), true);
32
+ assert.equal(result.pendingInvites[0].roleSid, "member");
38
33
  });
@@ -78,8 +78,9 @@ test("listPendingInvitesForUser returns raw pending invite rows for the action l
78
78
  });
79
79
 
80
80
  assert.equal(pendingInvites.length, 1);
81
- assert.equal(pendingInvites[0].tokenHash, tokenHash);
81
+ assert.equal(pendingInvites[0].token, encodeInviteTokenHash(tokenHash));
82
82
  assert.equal(pendingInvites[0].workspaceName, "TonyMobily3");
83
+ assert.equal(pendingInvites[0].status, "pending");
83
84
  });
84
85
 
85
86
  test("acceptInviteByToken accepts opaque invite token and resolves invite by decoded hash", async () => {
@@ -1,13 +1,25 @@
1
1
  import assert from "node:assert/strict";
2
2
  import test from "node:test";
3
+ import { AUTH_POLICY_CONTEXT_RESOLVER_TAG } from "@jskit-ai/auth-core/server/authPolicyContextResolverRegistry";
4
+ import { validateSchemaPayload } from "@jskit-ai/kernel/shared/validators";
3
5
  import { createWorkspaceServerScopeSupport } from "../src/server/support/workspaceServerScopeSupport.js";
4
6
  import { registerWorkspaceCore } from "../src/server/registerWorkspaceCore.js";
5
7
 
6
- test("workspace server scope support exposes the canonical workspace helper surface", () => {
8
+ test("workspace server scope support exposes the canonical workspace helper surface", async () => {
7
9
  const support = createWorkspaceServerScopeSupport();
8
10
 
9
11
  assert.equal(support.available, true);
10
- assert.equal(typeof support.paramsValidator?.normalize, "function");
12
+ assert.equal(typeof support.params?.schema, "object");
13
+ assert.equal(support.params?.mode, "patch");
14
+ assert.deepEqual(
15
+ await validateSchemaPayload(support.params, { workspaceSlug: " ACME " }, {
16
+ phase: "input",
17
+ context: "workspaceServerScopeSupport.params"
18
+ }),
19
+ {
20
+ workspaceSlug: "acme"
21
+ }
22
+ );
11
23
  assert.deepEqual(support.buildInputFromRouteParams({ workspaceSlug: " ACME " }), {
12
24
  workspaceSlug: "acme"
13
25
  });
@@ -34,12 +46,17 @@ test("workspace server scope support exposes the canonical workspace helper surf
34
46
 
35
47
  test("registerWorkspaceCore registers the workspace server scope support token", () => {
36
48
  const singletons = new Map();
49
+ const tags = new Map();
37
50
  const app = {
38
51
  singleton(token, factory) {
39
52
  singletons.set(token, factory);
40
53
  return this;
41
54
  },
42
- tag() {
55
+ tag(token, tagName) {
56
+ const key = String(tagName || "");
57
+ const list = tags.get(key) || [];
58
+ list.push(String(token || ""));
59
+ tags.set(key, list);
43
60
  return this;
44
61
  },
45
62
  has() {
@@ -50,6 +67,7 @@ test("registerWorkspaceCore registers the workspace server scope support token",
50
67
  registerWorkspaceCore(app);
51
68
 
52
69
  assert.equal(singletons.has("workspaces.server.scope-support"), true);
70
+ assert.deepEqual(tags.get(AUTH_POLICY_CONTEXT_RESOLVER_TAG), ["workspaces.core.authPolicyContextResolver"]);
53
71
  const support = singletons.get("workspaces.server.scope-support")();
54
72
  assert.equal(support.available, true);
55
73
  assert.equal(typeof support.buildInputFromRouteParams, "function");
@@ -1,11 +1,9 @@
1
1
  import assert from "node:assert/strict";
2
2
  import test from "node:test";
3
- import "../test-support/registerDefaultSettingsFields.js";
4
3
  import { workspaceDirectoryActions } from "../src/server/workspaceDirectory/workspaceDirectoryActions.js";
5
4
  import { workspacePendingInvitationsActions } from "../src/server/workspacePendingInvitations/workspacePendingInvitationsActions.js";
6
5
  import { workspaceMembersActions } from "../src/server/workspaceMembers/workspaceMembersActions.js";
7
6
  import { workspaceSettingsActions } from "../src/server/workspaceSettings/workspaceSettingsActions.js";
8
- import { workspaceResource } from "../src/shared/resources/workspaceResource.js";
9
7
 
10
8
  test("workspace settings actions live in their own action array", () => {
11
9
  assert.deepEqual(
@@ -35,18 +33,18 @@ test("workspace actions array no longer owns workspace settings actions", () =>
35
33
  );
36
34
  });
37
35
 
38
- test("workspace directory actions use the canonical workspace list resource output", () => {
36
+ test("workspace directory actions stay thin and defer output validation to routes", () => {
39
37
  const listAction = workspaceDirectoryActions.find((action) => action.id === "workspace.workspaces.list");
40
38
  assert.ok(listAction);
41
- assert.equal(listAction.outputValidator, workspaceResource.operations.list.outputValidator);
39
+ assert.equal(listAction.output, null);
42
40
  });
43
41
 
44
- test("workspace directory read/update actions use canonical workspace resource validators", () => {
42
+ test("workspace directory read/update actions stay thin and defer output validation to routes", () => {
45
43
  const readAction = workspaceDirectoryActions.find((action) => action.id === "workspace.workspaces.read");
46
44
  const updateAction = workspaceDirectoryActions.find((action) => action.id === "workspace.workspaces.update");
47
45
 
48
46
  assert.ok(readAction);
49
47
  assert.ok(updateAction);
50
- assert.equal(readAction.outputValidator, workspaceResource.operations.view.outputValidator);
51
- assert.equal(updateAction.outputValidator, workspaceResource.operations.patch.outputValidator);
48
+ assert.equal(readAction.output, null);
49
+ assert.equal(updateAction.output, null);
52
50
  });
@@ -0,0 +1,8 @@
1
+ import assert from "node:assert/strict";
2
+ import test from "node:test";
3
+ import { workspaceSettingsResource } from "../src/shared/resources/workspaceSettingsResource.js";
4
+
5
+ test("shared workspace settings resource declares workspace_id as the resource id column", () => {
6
+ assert.equal(workspaceSettingsResource.idProperty, "workspace_id");
7
+ assert.equal(workspaceSettingsResource.schema.id?.storage?.column, "workspace_id");
8
+ });