@jskit-ai/workspaces-core 0.1.22 → 0.1.23

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 (30) hide show
  1. package/package.descriptor.mjs +2 -2
  2. package/package.json +6 -6
  3. package/src/server/common/repositories/workspaceInvitesRepository.js +68 -65
  4. package/src/server/common/repositories/workspaceMembershipsRepository.js +83 -52
  5. package/src/server/common/repositories/workspacesRepository.js +42 -67
  6. package/src/server/common/resources/workspaceInvitesResource.js +207 -0
  7. package/src/server/common/resources/workspaceMembershipsResource.js +154 -0
  8. package/src/server/common/resources/workspacesResource.js +170 -0
  9. package/src/server/registerWorkspaceBootstrap.js +1 -1
  10. package/src/server/registerWorkspaceCore.js +3 -3
  11. package/src/server/registerWorkspaceRepositories.js +3 -3
  12. package/src/server/workspaceBootstrapContributor.js +4 -4
  13. package/src/server/workspaceMembers/registerWorkspaceMembers.js +2 -2
  14. package/src/server/workspacePendingInvitations/registerWorkspacePendingInvitations.js +2 -2
  15. package/src/server/workspaceSettings/registerWorkspaceSettings.js +2 -2
  16. package/src/server/workspaceSettings/workspaceSettingsRepository.js +5 -4
  17. package/src/shared/resources/workspaceMembersResource.js +1 -1
  18. package/src/shared/resources/workspacePendingInvitationsResource.js +1 -1
  19. package/src/shared/resources/workspaceResource.js +1 -1
  20. package/src/shared/resources/workspaceSettingsFields.js +10 -4
  21. package/src/shared/resources/workspaceSettingsResource.js +1 -1
  22. package/templates/packages/main/src/shared/resources/workspaceSettingsFields.js +9 -9
  23. package/test/exportsContract.test.js +3 -1
  24. package/test/registerWorkspaceBootstrap.test.js +1 -1
  25. package/test/registerWorkspaceSettings.test.js +1 -1
  26. package/test/usersRouteResources.test.js +1 -1
  27. package/test/workspaceBootstrapContributor.test.js +3 -3
  28. package/test/workspaceInvitesRepository.test.js +129 -18
  29. package/test/workspaceMembershipsRepository.test.js +212 -0
  30. package/test/workspacesRepository.test.js +253 -0
@@ -0,0 +1,253 @@
1
+ import assert from "node:assert/strict";
2
+ import test from "node:test";
3
+ import { toIsoString } from "@jskit-ai/database-runtime/shared";
4
+ import { createRepository } from "../src/server/common/repositories/workspacesRepository.js";
5
+
6
+ function createWorkspacesKnexStub({
7
+ rowById = new Map(),
8
+ rowBySlug = new Map(),
9
+ insertError = null,
10
+ membershipRows = []
11
+ } = {}) {
12
+ const state = {
13
+ insertPayload: null,
14
+ updatePayload: null
15
+ };
16
+
17
+ function buildWorkspacesQuery(tableName) {
18
+ const query = {
19
+ tableName,
20
+ selectedColumns: [],
21
+ whereCriteria: [],
22
+ orderByClauses: [],
23
+ select(...columns) {
24
+ this.selectedColumns = columns;
25
+ return this;
26
+ },
27
+ where(criteria) {
28
+ this.whereCriteria.push(criteria);
29
+ return this;
30
+ },
31
+ orderBy(column, direction) {
32
+ this.orderByClauses.push({ column, direction });
33
+ return this;
34
+ },
35
+ async first() {
36
+ const criteria = Object.assign({}, ...this.whereCriteria);
37
+ if (Object.hasOwn(criteria, "w.id")) {
38
+ return rowById.get(String(criteria["w.id"])) || null;
39
+ }
40
+ if (Object.hasOwn(criteria, "id")) {
41
+ return rowById.get(String(criteria.id)) || null;
42
+ }
43
+ if (Object.hasOwn(criteria, "w.slug")) {
44
+ return rowBySlug.get(String(criteria["w.slug"])) || null;
45
+ }
46
+ if (Object.hasOwn(criteria, "w.owner_user_id") && Object.hasOwn(criteria, "w.is_personal")) {
47
+ for (const row of rowById.values()) {
48
+ if (
49
+ String(row.owner_user_id) === String(criteria["w.owner_user_id"]) &&
50
+ Number(row.is_personal) === Number(criteria["w.is_personal"])
51
+ ) {
52
+ return row;
53
+ }
54
+ }
55
+ }
56
+ return null;
57
+ },
58
+ async insert(payload) {
59
+ state.insertPayload = payload;
60
+ if (insertError) {
61
+ throw insertError;
62
+ }
63
+ return [1];
64
+ },
65
+ async update(payload) {
66
+ state.updatePayload = payload;
67
+ return 1;
68
+ }
69
+ };
70
+
71
+ return query;
72
+ }
73
+
74
+ function buildMembershipsQuery() {
75
+ return {
76
+ join() {
77
+ return this;
78
+ },
79
+ where() {
80
+ return this;
81
+ },
82
+ whereNull() {
83
+ return this;
84
+ },
85
+ orderBy() {
86
+ return this;
87
+ },
88
+ select() {
89
+ return Promise.resolve([...membershipRows]);
90
+ }
91
+ };
92
+ }
93
+
94
+ function knex(tableName) {
95
+ if (tableName === "workspaces" || tableName === "workspaces as w") {
96
+ return buildWorkspacesQuery(tableName);
97
+ }
98
+ if (tableName === "workspace_memberships as wm") {
99
+ return buildMembershipsQuery();
100
+ }
101
+ throw new Error(`Unexpected table ${tableName}`);
102
+ }
103
+
104
+ knex.transaction = async (work) => work(knex);
105
+
106
+ return { knex, state };
107
+ }
108
+
109
+ test("workspacesRepository.findById normalizes internal workspace fields via the internal resource", async () => {
110
+ const { knex } = createWorkspacesKnexStub({
111
+ rowById: new Map([
112
+ [
113
+ "7",
114
+ {
115
+ id: 7,
116
+ slug: "tonymobily3",
117
+ name: "TonyMobily3",
118
+ owner_user_id: 9,
119
+ is_personal: 1,
120
+ avatar_url: "",
121
+ created_at: "2026-03-09 00:26:35.710",
122
+ updated_at: "2026-03-10 00:26:35.710",
123
+ deleted_at: null
124
+ }
125
+ ]
126
+ ])
127
+ });
128
+ const repository = createRepository(knex);
129
+
130
+ const workspace = await repository.findById("7");
131
+
132
+ assert.deepEqual(workspace, {
133
+ id: "7",
134
+ slug: "tonymobily3",
135
+ name: "TonyMobily3",
136
+ ownerUserId: "9",
137
+ isPersonal: true,
138
+ avatarUrl: "",
139
+ createdAt: toIsoString("2026-03-09 00:26:35.710"),
140
+ updatedAt: toIsoString("2026-03-10 00:26:35.710"),
141
+ deletedAt: null
142
+ });
143
+ });
144
+
145
+ test("workspacesRepository.insert uses runtime normalization and timestamp columns", async () => {
146
+ const insertedRow = {
147
+ id: 1,
148
+ slug: "tonymobily3",
149
+ name: "TonyMobily3",
150
+ owner_user_id: 9,
151
+ is_personal: 0,
152
+ avatar_url: "",
153
+ created_at: "2026-03-09 00:26:35.710",
154
+ updated_at: "2026-03-09 00:26:35.710",
155
+ deleted_at: null
156
+ };
157
+ const { knex, state } = createWorkspacesKnexStub({
158
+ rowById: new Map([["1", insertedRow]])
159
+ });
160
+ const repository = createRepository(knex);
161
+
162
+ const inserted = await repository.insert({
163
+ slug: "TonyMobily3",
164
+ name: "TonyMobily3",
165
+ ownerUserId: "9"
166
+ });
167
+
168
+ assert.equal(state.insertPayload.slug, "tonymobily3");
169
+ assert.equal(state.insertPayload.name, "TonyMobily3");
170
+ assert.equal(state.insertPayload.owner_user_id, "9");
171
+ assert.equal(state.insertPayload.is_personal, false);
172
+ assert.equal(state.insertPayload.avatar_url, "");
173
+ assert.equal(typeof state.insertPayload.created_at, "string");
174
+ assert.equal(typeof state.insertPayload.updated_at, "string");
175
+ assert.deepEqual(inserted, {
176
+ id: "1",
177
+ slug: "tonymobily3",
178
+ name: "TonyMobily3",
179
+ ownerUserId: "9",
180
+ isPersonal: false,
181
+ avatarUrl: "",
182
+ createdAt: toIsoString("2026-03-09 00:26:35.710"),
183
+ updatedAt: toIsoString("2026-03-09 00:26:35.710"),
184
+ deletedAt: null
185
+ });
186
+ });
187
+
188
+ test("workspacesRepository.insert falls back to slug lookup on duplicate workspace slug", async () => {
189
+ const existingRow = {
190
+ id: 12,
191
+ slug: "shared-workspace",
192
+ name: "Shared Workspace",
193
+ owner_user_id: 9,
194
+ is_personal: 0,
195
+ avatar_url: "",
196
+ created_at: "2026-03-09 00:26:35.710",
197
+ updated_at: "2026-03-09 00:26:35.710",
198
+ deleted_at: null
199
+ };
200
+ const { knex } = createWorkspacesKnexStub({
201
+ rowBySlug: new Map([["shared-workspace", existingRow]]),
202
+ insertError: { code: "ER_DUP_ENTRY" }
203
+ });
204
+ const repository = createRepository(knex);
205
+
206
+ const inserted = await repository.insert({
207
+ slug: "shared-workspace",
208
+ name: "Shared Workspace",
209
+ ownerUserId: "9"
210
+ });
211
+
212
+ assert.equal(inserted?.id, "12");
213
+ assert.equal(inserted?.slug, "shared-workspace");
214
+ });
215
+
216
+ test("workspacesRepository.listForUserId keeps membership-specific fields while normalizing workspace fields", async () => {
217
+ const { knex } = createWorkspacesKnexStub({
218
+ membershipRows: [
219
+ {
220
+ id: 7,
221
+ slug: "tonymobily3",
222
+ name: "TonyMobily3",
223
+ owner_user_id: 9,
224
+ is_personal: 1,
225
+ avatar_url: "",
226
+ created_at: "2026-03-09 00:26:35.710",
227
+ updated_at: "2026-03-10 00:26:35.710",
228
+ deleted_at: null,
229
+ role_sid: "owner",
230
+ membership_status: "active"
231
+ }
232
+ ]
233
+ });
234
+ const repository = createRepository(knex);
235
+
236
+ const rows = await repository.listForUserId("9");
237
+
238
+ assert.deepEqual(rows, [
239
+ {
240
+ id: "7",
241
+ slug: "tonymobily3",
242
+ name: "TonyMobily3",
243
+ ownerUserId: "9",
244
+ isPersonal: true,
245
+ avatarUrl: "",
246
+ createdAt: toIsoString("2026-03-09 00:26:35.710"),
247
+ updatedAt: toIsoString("2026-03-10 00:26:35.710"),
248
+ deletedAt: null,
249
+ roleSid: "owner",
250
+ membershipStatus: "active"
251
+ }
252
+ ]);
253
+ });