@jskit-ai/workspaces-core 0.1.32 → 0.1.34
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.
- package/package.descriptor.mjs +5 -5
- package/package.json +9 -9
- package/src/server/common/repositories/workspaceMembershipsRepository.js +10 -3
- package/src/server/common/services/workspaceContextService.js +2 -2
- package/src/server/registerWorkspaceCore.js +3 -3
- package/test/workspaceMembershipsRepository.test.js +23 -1
- package/test/workspaceServerScopeSupport.test.js +56 -0
- package/test/workspaceService.test.js +26 -5
package/package.descriptor.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export default Object.freeze({
|
|
2
2
|
packageVersion: 1,
|
|
3
3
|
packageId: "@jskit-ai/workspaces-core",
|
|
4
|
-
version: "0.1.
|
|
4
|
+
version: "0.1.34",
|
|
5
5
|
kind: "runtime",
|
|
6
6
|
description: "Workspace tenancy runtime plus HTTP routes, role catalog, and workspace config scaffolding.",
|
|
7
7
|
dependsOn: [
|
|
@@ -116,10 +116,10 @@ export default Object.freeze({
|
|
|
116
116
|
mutations: {
|
|
117
117
|
dependencies: {
|
|
118
118
|
runtime: {
|
|
119
|
-
"@jskit-ai/json-rest-api-core": "0.1.
|
|
120
|
-
"@jskit-ai/resource-core": "0.1.
|
|
121
|
-
"@jskit-ai/resource-crud-core": "0.1.
|
|
122
|
-
"@jskit-ai/users-core": "0.1.
|
|
119
|
+
"@jskit-ai/json-rest-api-core": "0.1.3",
|
|
120
|
+
"@jskit-ai/resource-core": "0.1.3",
|
|
121
|
+
"@jskit-ai/resource-crud-core": "0.1.3",
|
|
122
|
+
"@jskit-ai/users-core": "0.1.68"
|
|
123
123
|
},
|
|
124
124
|
dev: {}
|
|
125
125
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jskit-ai/workspaces-core",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.34",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"test": "node --test"
|
|
@@ -17,14 +17,14 @@
|
|
|
17
17
|
"./shared/resources/workspaceSettingsResource": "./src/shared/resources/workspaceSettingsResource.js"
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"@jskit-ai/auth-core": "0.1.
|
|
21
|
-
"@jskit-ai/database-runtime": "0.1.
|
|
22
|
-
"@jskit-ai/http-runtime": "0.1.
|
|
23
|
-
"@jskit-ai/json-rest-api-core": "0.1.
|
|
24
|
-
"@jskit-ai/kernel": "0.1.
|
|
25
|
-
"@jskit-ai/resource-crud-core": "0.1.
|
|
26
|
-
"@jskit-ai/resource-core": "0.1.
|
|
27
|
-
"@jskit-ai/users-core": "0.1.
|
|
20
|
+
"@jskit-ai/auth-core": "0.1.57",
|
|
21
|
+
"@jskit-ai/database-runtime": "0.1.58",
|
|
22
|
+
"@jskit-ai/http-runtime": "0.1.57",
|
|
23
|
+
"@jskit-ai/json-rest-api-core": "0.1.3",
|
|
24
|
+
"@jskit-ai/kernel": "0.1.58",
|
|
25
|
+
"@jskit-ai/resource-crud-core": "0.1.3",
|
|
26
|
+
"@jskit-ai/resource-core": "0.1.3",
|
|
27
|
+
"@jskit-ai/users-core": "0.1.68",
|
|
28
28
|
"json-rest-schema": "1.x.x"
|
|
29
29
|
}
|
|
30
30
|
}
|
|
@@ -84,12 +84,19 @@ function createRepository({ api, knex } = {}) {
|
|
|
84
84
|
|
|
85
85
|
const withTransaction = createWithTransaction(knex);
|
|
86
86
|
|
|
87
|
-
async function queryMemberships(filters = {}, options = {}, {
|
|
87
|
+
async function queryMemberships(filters = {}, options = {}, { include = [] } = {}) {
|
|
88
|
+
const normalizedInclude = Array.from(
|
|
89
|
+
new Set(
|
|
90
|
+
(Array.isArray(include) ? include : [])
|
|
91
|
+
.map((entry) => normalizeText(entry))
|
|
92
|
+
.filter(Boolean)
|
|
93
|
+
)
|
|
94
|
+
);
|
|
88
95
|
const result = await api.resources.workspaceMemberships.query(
|
|
89
96
|
{
|
|
90
97
|
queryParams: {
|
|
91
98
|
filters,
|
|
92
|
-
...(
|
|
99
|
+
...(normalizedInclude.length > 0 ? { include: normalizedInclude } : {})
|
|
93
100
|
},
|
|
94
101
|
transaction: options?.trx || null,
|
|
95
102
|
simplified: false
|
|
@@ -263,7 +270,7 @@ function createRepository({ api, knex } = {}) {
|
|
|
263
270
|
status: "active"
|
|
264
271
|
},
|
|
265
272
|
options,
|
|
266
|
-
{
|
|
273
|
+
{ include: ["user"] }
|
|
267
274
|
);
|
|
268
275
|
|
|
269
276
|
const members = rows
|
|
@@ -155,7 +155,7 @@ function createService({
|
|
|
155
155
|
return listWorkspacesForUser(user, options);
|
|
156
156
|
}
|
|
157
157
|
|
|
158
|
-
async function
|
|
158
|
+
async function ensureProvisionedWorkspaceForAuthenticatedUser(user, options = {}) {
|
|
159
159
|
const normalizedUserId = normalizeRecordId(user?.id, { fallback: null });
|
|
160
160
|
if (!normalizedUserId) {
|
|
161
161
|
throw new AppError(400, "Invalid authenticated user payload.");
|
|
@@ -270,7 +270,7 @@ function createService({
|
|
|
270
270
|
buildWorkspaceBaseSlug,
|
|
271
271
|
hashInviteToken,
|
|
272
272
|
ensurePersonalWorkspaceForUser,
|
|
273
|
-
|
|
273
|
+
ensureProvisionedWorkspaceForAuthenticatedUser,
|
|
274
274
|
createWorkspaceForAuthenticatedUser,
|
|
275
275
|
getWorkspaceForAuthenticatedUser,
|
|
276
276
|
updateWorkspaceForAuthenticatedUser,
|
|
@@ -67,12 +67,12 @@ function registerWorkspaceCore(app) {
|
|
|
67
67
|
return Object.freeze({
|
|
68
68
|
contributorId: "workspaces.core.profileSync",
|
|
69
69
|
order: 100,
|
|
70
|
-
async afterIdentityProfileSynced({ profile,
|
|
71
|
-
if (!
|
|
70
|
+
async afterIdentityProfileSynced({ profile, options } = {}) {
|
|
71
|
+
if (!profile || typeof workspaceService?.ensureProvisionedWorkspaceForAuthenticatedUser !== "function") {
|
|
72
72
|
return;
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
-
await workspaceService.
|
|
75
|
+
await workspaceService.ensureProvisionedWorkspaceForAuthenticatedUser(profile, options);
|
|
76
76
|
}
|
|
77
77
|
});
|
|
78
78
|
});
|
|
@@ -48,13 +48,15 @@ function createWorkspaceMembershipsApiStub({
|
|
|
48
48
|
} = {}) {
|
|
49
49
|
const state = {
|
|
50
50
|
postPayload: null,
|
|
51
|
-
patchPayload: null
|
|
51
|
+
patchPayload: null,
|
|
52
|
+
queryCalls: []
|
|
52
53
|
};
|
|
53
54
|
|
|
54
55
|
const api = {
|
|
55
56
|
resources: {
|
|
56
57
|
workspaceMemberships: {
|
|
57
58
|
async query({ queryParams }) {
|
|
59
|
+
state.queryCalls.push(queryParams || {});
|
|
58
60
|
const filters = queryParams?.filters || {};
|
|
59
61
|
const includeUser = Array.isArray(queryParams?.include) && queryParams.include.includes("user");
|
|
60
62
|
|
|
@@ -250,3 +252,23 @@ test("workspaceMembershipsRepository.listActiveByWorkspaceId keeps summary rows
|
|
|
250
252
|
}
|
|
251
253
|
]);
|
|
252
254
|
});
|
|
255
|
+
|
|
256
|
+
test("workspaceMembershipsRepository.listActiveWorkspaceIdsByUserId returns normalized workspace ids from the canonical resource", async () => {
|
|
257
|
+
const membershipRow = {
|
|
258
|
+
id: "11",
|
|
259
|
+
workspace: { id: "7" },
|
|
260
|
+
user: { id: "9" },
|
|
261
|
+
roleSid: "owner",
|
|
262
|
+
status: "active",
|
|
263
|
+
createdAt: "2026-03-09 00:26:35.710",
|
|
264
|
+
updatedAt: "2026-03-10 00:26:35.710"
|
|
265
|
+
};
|
|
266
|
+
const { api } = createWorkspaceMembershipsApiStub({
|
|
267
|
+
rowByComposite: new Map([["7:9", membershipRow]])
|
|
268
|
+
});
|
|
269
|
+
const repository = createRepository({ api, knex: createKnexStub() });
|
|
270
|
+
|
|
271
|
+
const workspaceIds = await repository.listActiveWorkspaceIdsByUserId("9");
|
|
272
|
+
|
|
273
|
+
assert.deepEqual(workspaceIds, ["7"]);
|
|
274
|
+
});
|
|
@@ -2,6 +2,7 @@ import assert from "node:assert/strict";
|
|
|
2
2
|
import test from "node:test";
|
|
3
3
|
import { AUTH_POLICY_CONTEXT_RESOLVER_TAG } from "@jskit-ai/auth-core/server/authPolicyContextResolverRegistry";
|
|
4
4
|
import { validateSchemaPayload } from "@jskit-ai/kernel/shared/validators";
|
|
5
|
+
import { PROFILE_SYNC_LIFECYCLE_CONTRIBUTOR_TAG } from "@jskit-ai/users-core/server/profileSyncLifecycleContributorRegistry";
|
|
5
6
|
import { createWorkspaceServerScopeSupport } from "../src/server/support/workspaceServerScopeSupport.js";
|
|
6
7
|
import { registerWorkspaceCore } from "../src/server/registerWorkspaceCore.js";
|
|
7
8
|
|
|
@@ -73,3 +74,58 @@ test("registerWorkspaceCore registers the workspace server scope support token",
|
|
|
73
74
|
assert.equal(typeof support.buildInputFromRouteParams, "function");
|
|
74
75
|
assert.equal(typeof support.resolveWorkspace, "function");
|
|
75
76
|
});
|
|
77
|
+
|
|
78
|
+
test("registerWorkspaceCore ensures personal workspace provisioning on every authenticated profile sync", async () => {
|
|
79
|
+
const singletons = new Map();
|
|
80
|
+
const tags = new Map();
|
|
81
|
+
const app = {
|
|
82
|
+
singleton(token, factory) {
|
|
83
|
+
singletons.set(token, factory);
|
|
84
|
+
return this;
|
|
85
|
+
},
|
|
86
|
+
tag(token, tagName) {
|
|
87
|
+
const key = String(tagName || "");
|
|
88
|
+
const list = tags.get(key) || [];
|
|
89
|
+
list.push(String(token || ""));
|
|
90
|
+
tags.set(key, list);
|
|
91
|
+
return this;
|
|
92
|
+
},
|
|
93
|
+
has() {
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
registerWorkspaceCore(app);
|
|
99
|
+
|
|
100
|
+
assert.deepEqual(tags.get(PROFILE_SYNC_LIFECYCLE_CONTRIBUTOR_TAG), ["workspaces.core.profileSyncLifecycleContributor"]);
|
|
101
|
+
|
|
102
|
+
const lifecycleFactory = singletons.get("workspaces.core.profileSyncLifecycleContributor");
|
|
103
|
+
assert.equal(typeof lifecycleFactory, "function");
|
|
104
|
+
|
|
105
|
+
const calls = [];
|
|
106
|
+
const contributor = lifecycleFactory({
|
|
107
|
+
make(token) {
|
|
108
|
+
if (token === "workspaces.service") {
|
|
109
|
+
return {
|
|
110
|
+
async ensureProvisionedWorkspaceForAuthenticatedUser(profile, options = {}) {
|
|
111
|
+
calls.push({ profile, options });
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
throw new Error(`Unexpected token: ${token}`);
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
await contributor.afterIdentityProfileSynced({
|
|
120
|
+
profile: { id: "7", username: "tonymobily" },
|
|
121
|
+
created: false,
|
|
122
|
+
options: { trx: { id: "tx-1" } }
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
assert.deepEqual(calls, [
|
|
126
|
+
{
|
|
127
|
+
profile: { id: "7", username: "tonymobily" },
|
|
128
|
+
options: { trx: { id: "tx-1" } }
|
|
129
|
+
}
|
|
130
|
+
]);
|
|
131
|
+
});
|
|
@@ -41,7 +41,8 @@ function createWorkspaceServiceFixture({
|
|
|
41
41
|
listForUserId: 0,
|
|
42
42
|
insert: 0,
|
|
43
43
|
updateById: 0,
|
|
44
|
-
ensureOwnerMembership: 0
|
|
44
|
+
ensureOwnerMembership: 0,
|
|
45
|
+
ensureWorkspaceSettings: 0
|
|
45
46
|
};
|
|
46
47
|
let nextWorkspaceId = 10;
|
|
47
48
|
const personalWorkspaceState =
|
|
@@ -167,6 +168,7 @@ function createWorkspaceServiceFixture({
|
|
|
167
168
|
},
|
|
168
169
|
workspaceSettingsRepository: {
|
|
169
170
|
async ensureForWorkspaceId() {
|
|
171
|
+
calls.ensureWorkspaceSettings += 1;
|
|
170
172
|
return {
|
|
171
173
|
invitesEnabled: true
|
|
172
174
|
};
|
|
@@ -258,13 +260,13 @@ test("workspaceService.listWorkspacesForUser returns all active memberships in p
|
|
|
258
260
|
assert.equal(calls.listForUserId, 1);
|
|
259
261
|
});
|
|
260
262
|
|
|
261
|
-
test("workspaceService.
|
|
263
|
+
test("workspaceService.ensureProvisionedWorkspaceForAuthenticatedUser provisions personal workspace only in personal tenancy", async () => {
|
|
262
264
|
const { service, calls, insertedPayloads } = createWorkspaceServiceFixture({
|
|
263
265
|
tenancyMode: "personal",
|
|
264
266
|
personalWorkspace: null
|
|
265
267
|
});
|
|
266
268
|
|
|
267
|
-
const workspace = await service.
|
|
269
|
+
const workspace = await service.ensureProvisionedWorkspaceForAuthenticatedUser({
|
|
268
270
|
id: "7",
|
|
269
271
|
email: "chiaramobily@gmail.com",
|
|
270
272
|
displayName: "Chiara"
|
|
@@ -274,15 +276,34 @@ test("workspaceService.provisionWorkspaceForNewUser provisions personal workspac
|
|
|
274
276
|
assert.equal(calls.findPersonalByOwnerUserId, 1);
|
|
275
277
|
assert.equal(calls.insert, 1);
|
|
276
278
|
assert.equal(calls.ensureOwnerMembership, 1);
|
|
279
|
+
assert.equal(calls.ensureWorkspaceSettings, 1);
|
|
277
280
|
assert.equal(insertedPayloads[0].isPersonal, true);
|
|
278
281
|
});
|
|
279
282
|
|
|
280
|
-
test("workspaceService.
|
|
283
|
+
test("workspaceService.ensureProvisionedWorkspaceForAuthenticatedUser reuses the existing personal workspace in personal tenancy", async () => {
|
|
284
|
+
const { service, calls } = createWorkspaceServiceFixture({
|
|
285
|
+
tenancyMode: "personal"
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
const workspace = await service.ensureProvisionedWorkspaceForAuthenticatedUser({
|
|
289
|
+
id: "7",
|
|
290
|
+
email: "chiaramobily@gmail.com",
|
|
291
|
+
displayName: "Chiara"
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
assert.equal(workspace.id, "1");
|
|
295
|
+
assert.equal(calls.findPersonalByOwnerUserId, 1);
|
|
296
|
+
assert.equal(calls.insert, 0);
|
|
297
|
+
assert.equal(calls.ensureOwnerMembership, 1);
|
|
298
|
+
assert.equal(calls.ensureWorkspaceSettings, 1);
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
test("workspaceService.ensureProvisionedWorkspaceForAuthenticatedUser is a no-op outside personal tenancy", async () => {
|
|
281
302
|
const { service, calls } = createWorkspaceServiceFixture({
|
|
282
303
|
tenancyMode: "workspaces"
|
|
283
304
|
});
|
|
284
305
|
|
|
285
|
-
const result = await service.
|
|
306
|
+
const result = await service.ensureProvisionedWorkspaceForAuthenticatedUser({
|
|
286
307
|
id: "7",
|
|
287
308
|
email: "chiaramobily@gmail.com",
|
|
288
309
|
displayName: "Chiara"
|