@jskit-ai/users-web 0.1.46 → 0.1.48

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.
@@ -1,7 +1,7 @@
1
1
  export default Object.freeze({
2
2
  packageVersion: 1,
3
3
  packageId: "@jskit-ai/users-web",
4
- version: "0.1.46",
4
+ version: "0.1.48",
5
5
  kind: "runtime",
6
6
  description: "Users web module: account/profile UI plus shared shell link components.",
7
7
  dependsOn: [
@@ -153,12 +153,12 @@ export default Object.freeze({
153
153
  runtime: {
154
154
  "@tanstack/vue-query": "5.92.12",
155
155
  "@mdi/js": "^7.4.47",
156
- "@jskit-ai/http-runtime": "0.1.30",
157
- "@jskit-ai/realtime": "0.1.30",
158
- "@jskit-ai/kernel": "0.1.31",
159
- "@jskit-ai/shell-web": "0.1.30",
160
- "@jskit-ai/uploads-image-web": "0.1.9",
161
- "@jskit-ai/users-core": "0.1.41",
156
+ "@jskit-ai/http-runtime": "0.1.32",
157
+ "@jskit-ai/realtime": "0.1.32",
158
+ "@jskit-ai/kernel": "0.1.33",
159
+ "@jskit-ai/shell-web": "0.1.32",
160
+ "@jskit-ai/uploads-image-web": "0.1.11",
161
+ "@jskit-ai/users-core": "0.1.43",
162
162
  vuetify: "^4.0.0"
163
163
  },
164
164
  dev: {}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jskit-ai/users-web",
3
- "version": "0.1.46",
3
+ "version": "0.1.48",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "test": "node --test"
@@ -28,12 +28,12 @@
28
28
  "dependencies": {
29
29
  "@tanstack/vue-query": "5.92.12",
30
30
  "@mdi/js": "^7.4.47",
31
- "@jskit-ai/http-runtime": "0.1.30",
32
- "@jskit-ai/kernel": "0.1.31",
33
- "@jskit-ai/realtime": "0.1.30",
34
- "@jskit-ai/shell-web": "0.1.30",
35
- "@jskit-ai/uploads-image-web": "0.1.9",
36
- "@jskit-ai/users-core": "0.1.41",
31
+ "@jskit-ai/http-runtime": "0.1.32",
32
+ "@jskit-ai/kernel": "0.1.33",
33
+ "@jskit-ai/realtime": "0.1.32",
34
+ "@jskit-ai/shell-web": "0.1.32",
35
+ "@jskit-ai/uploads-image-web": "0.1.11",
36
+ "@jskit-ai/users-core": "0.1.43",
37
37
  "vuetify": "^4.0.0"
38
38
  }
39
39
  }
@@ -168,6 +168,7 @@
168
168
  <script setup>
169
169
  import { computed, toRefs, unref } from "vue";
170
170
  import { formatDateTime as formatKernelDateTime } from "@jskit-ai/kernel/shared/support";
171
+ import { normalizeRecordId } from "@jskit-ai/kernel/shared/support/normalize";
171
172
  import { requireBoolean, requireFunction, requireRecord } from "../support/contractGuards.js";
172
173
 
173
174
  const props = defineProps({
@@ -188,11 +189,11 @@ const props = defineProps({
188
189
  required: true
189
190
  },
190
191
  revokeInviteId: {
191
- type: Number,
192
+ type: String,
192
193
  required: true
193
194
  },
194
195
  removeMemberUserId: {
195
- type: Number,
196
+ type: String,
196
197
  required: true
197
198
  },
198
199
  status: {
@@ -352,11 +353,11 @@ function isMemberRemoveLocked(member) {
352
353
  }
353
354
 
354
355
  function isRevokeInviteLoading(inviteId) {
355
- return isRevokingInvite.value && revokeInviteId.value === Number(inviteId || 0);
356
+ return isRevokingInvite.value && revokeInviteId.value === normalizeRecordId(inviteId, { fallback: "" });
356
357
  }
357
358
 
358
359
  function isRemoveMemberLoading(memberUserId) {
359
- return isRemovingMember.value && removeMemberUserId.value === Number(memberUserId || 0);
360
+ return isRemovingMember.value && removeMemberUserId.value === normalizeRecordId(memberUserId, { fallback: "" });
360
361
  }
361
362
 
362
363
  async function onSubmitInvite() {
@@ -21,6 +21,7 @@
21
21
  <script setup>
22
22
  import { computed, reactive, ref, watch } from "vue";
23
23
  import { formatDateTime } from "@jskit-ai/kernel/shared/support";
24
+ import { normalizeRecordId } from "@jskit-ai/kernel/shared/support/normalize";
24
25
  import MembersAdminClientElement from "./MembersAdminClientElement.vue";
25
26
  import { useCommand } from "../composables/useCommand.js";
26
27
  import { useList } from "../composables/records/useList.js";
@@ -61,8 +62,8 @@ const collections = reactive({
61
62
  const inviteFeedback = useUiFeedback();
62
63
  const membersFeedback = useUiFeedback();
63
64
  const teamFeedback = useUiFeedback();
64
- const revokeInviteId = ref(0);
65
- const removeMemberUserId = ref(0);
65
+ const revokeInviteId = ref("");
66
+ const removeMemberUserId = ref("");
66
67
 
67
68
  const { route, currentSurfaceId, workspaceSlugFromRoute, mergePlacementContext } =
68
69
  useWorkspaceRouteContext();
@@ -83,7 +84,8 @@ const workspaceInvitesApiPath = computed(() =>
83
84
  );
84
85
 
85
86
  function workspaceMembersPath(memberId) {
86
- return `${workspaceMembersApiPath.value}/${Number(memberId || 0)}`;
87
+ const normalizedMemberId = encodeURIComponent(String(memberId || "").trim());
88
+ return `${workspaceMembersApiPath.value}/${normalizedMemberId}`;
87
89
  }
88
90
 
89
91
  function workspaceInvitePath(inviteId) {
@@ -145,8 +147,8 @@ function resetViewState() {
145
147
  collections.members = [];
146
148
  collections.invites = [];
147
149
  clearRoleOptions();
148
- revokeInviteId.value = 0;
149
- removeMemberUserId.value = 0;
150
+ revokeInviteId.value = "";
151
+ removeMemberUserId.value = "";
150
152
  }
151
153
 
152
154
  function toRoleTitle(roleSid) {
@@ -223,7 +225,7 @@ function normalizeMembers(entries) {
223
225
  return source.map((entry) => {
224
226
  const value = entry && typeof entry === "object" ? entry : {};
225
227
  return {
226
- userId: Number(value.userId || 0),
228
+ userId: normalizeRecordId(value.userId, { fallback: "" }),
227
229
  roleSid: String(value.roleSid || "").trim().toLowerCase(),
228
230
  status: String(value.status || "").trim().toLowerCase(),
229
231
  displayName: String(value.displayName || "").trim(),
@@ -238,12 +240,12 @@ function normalizeInvites(entries) {
238
240
  return source.map((entry) => {
239
241
  const value = entry && typeof entry === "object" ? entry : {};
240
242
  return {
241
- id: Number(value.id || 0),
243
+ id: normalizeRecordId(value.id, { fallback: "" }),
242
244
  email: String(value.email || "").trim().toLowerCase(),
243
245
  roleSid: String(value.roleSid || "").trim().toLowerCase(),
244
246
  status: String(value.status || "").trim().toLowerCase(),
245
247
  expiresAt: value.expiresAt || "",
246
- invitedByUserId: value.invitedByUserId == null ? null : Number(value.invitedByUserId)
248
+ invitedByUserId: normalizeRecordId(value.invitedByUserId, { fallback: null })
247
249
  };
248
250
  });
249
251
  }
@@ -576,7 +578,7 @@ async function submitRevokeInvite(inviteId) {
576
578
  return;
577
579
  }
578
580
 
579
- revokeInviteId.value = Number(inviteId || 0);
581
+ revokeInviteId.value = normalizeRecordId(inviteId, { fallback: "" });
580
582
  teamFeedback.clear();
581
583
 
582
584
  try {
@@ -591,7 +593,7 @@ async function submitRevokeInvite(inviteId) {
591
593
  } catch (error) {
592
594
  teamFeedback.error(error, "Unable to revoke invite.");
593
595
  } finally {
594
- revokeInviteId.value = 0;
596
+ revokeInviteId.value = "";
595
597
  }
596
598
  }
597
599
 
@@ -603,8 +605,8 @@ async function submitMemberRoleUpdate(member, roleSid) {
603
605
  membersFeedback.clear();
604
606
 
605
607
  try {
606
- const memberUserId = Number(member?.userId || 0);
607
- if (!Number.isInteger(memberUserId) || memberUserId < 1) {
608
+ const memberUserId = normalizeRecordId(member?.userId, { fallback: null });
609
+ if (!memberUserId) {
608
610
  throw new Error("Member user id is invalid.");
609
611
  }
610
612
 
@@ -630,12 +632,12 @@ async function submitRemoveMember(member) {
630
632
  membersFeedback.clear();
631
633
 
632
634
  try {
633
- const memberUserId = Number(member?.userId || 0);
634
- if (!Number.isInteger(memberUserId) || memberUserId < 1) {
635
+ const memberUserId = normalizeRecordId(member?.userId, { fallback: null });
636
+ if (!memberUserId) {
635
637
  throw new Error("Member user id is invalid.");
636
638
  }
637
639
 
638
- removeMemberUserId.value = memberUserId;
640
+ removeMemberUserId.value = normalizeRecordId(memberUserId, { fallback: "" });
639
641
  await memberRemoveCommand.run({
640
642
  memberUserId
641
643
  });
@@ -647,7 +649,7 @@ async function submitRemoveMember(member) {
647
649
  } catch (error) {
648
650
  membersFeedback.error(error, "Unable to remove member.");
649
651
  } finally {
650
- removeMemberUserId.value = 0;
652
+ removeMemberUserId.value = "";
651
653
  }
652
654
  }
653
655
  </script>
@@ -3,6 +3,7 @@ import {
3
3
  normalizeReturnToPath as normalizeSharedReturnToPath,
4
4
  resolveAllowedOriginsFromPlacementContext
5
5
  } from "@jskit-ai/kernel/shared/support";
6
+ import { normalizeRecordId } from "@jskit-ai/kernel/shared/support/normalize";
6
7
  import { normalizeRecord } from "../../support/runtimeNormalization.js";
7
8
 
8
9
  function normalizeReturnToPath(value, { fallback = "/", accountSettingsPath = "/account", allowedOrigins = [] } = {}) {
@@ -27,9 +28,9 @@ function normalizePendingInvite(entry) {
27
28
  return null;
28
29
  }
29
30
 
30
- const id = Number(entry.id);
31
- const workspaceId = Number(entry.workspaceId);
32
- if (!Number.isInteger(id) || id < 1 || !Number.isInteger(workspaceId) || workspaceId < 1) {
31
+ const id = normalizeRecordId(entry.id, { fallback: null });
32
+ const workspaceId = normalizeRecordId(entry.workspaceId, { fallback: null });
33
+ if (!id || !workspaceId) {
33
34
  return null;
34
35
  }
35
36
 
@@ -1,3 +1,5 @@
1
+ import { normalizeRecordId } from "@jskit-ai/kernel/shared/support/normalize";
2
+
1
3
  function buildBootstrapApiPath(workspaceSlug = "") {
2
4
  const normalizedWorkspaceSlug = String(workspaceSlug || "").trim();
3
5
  if (!normalizedWorkspaceSlug) {
@@ -15,9 +17,9 @@ function normalizeWorkspaceEntry(entry) {
15
17
  return null;
16
18
  }
17
19
 
18
- const id = Number(entry.id);
20
+ const id = normalizeRecordId(entry.id, { fallback: null });
19
21
  const slug = String(entry.slug || "").trim();
20
- if (!Number.isInteger(id) || id < 1 || !slug) {
22
+ if (!id || !slug) {
21
23
  return null;
22
24
  }
23
25
 
@@ -69,8 +71,8 @@ function resolvePlacementUserFromBootstrapPayload(payload = {}, currentUser = nu
69
71
  const fallbackUser = currentUser && typeof currentUser === "object" ? currentUser : {};
70
72
  const nextUser = {};
71
73
 
72
- const userId = Number(session.userId || fallbackUser.id || 0);
73
- if (Number.isInteger(userId) && userId > 0) {
74
+ const userId = normalizeRecordId(session.userId || fallbackUser.id, { fallback: null });
75
+ if (userId) {
74
76
  nextUser.id = userId;
75
77
  }
76
78
 
@@ -17,7 +17,7 @@ test("resolvePlacementUserFromBootstrapPayload maps profile fields used by place
17
17
  const user = resolvePlacementUserFromBootstrapPayload({
18
18
  session: {
19
19
  authenticated: true,
20
- userId: 42
20
+ userId: "42"
21
21
  },
22
22
  profile: {
23
23
  displayName: "Ada Lovelace",
@@ -29,7 +29,7 @@ test("resolvePlacementUserFromBootstrapPayload maps profile fields used by place
29
29
  });
30
30
 
31
31
  assert.deepEqual(user, {
32
- id: 42,
32
+ id: "42",
33
33
  displayName: "Ada Lovelace",
34
34
  name: "Ada Lovelace",
35
35
  email: "ada@example.com",
@@ -245,7 +245,7 @@ test("bootstrap placement runtime writes user/workspace/permissions into placeme
245
245
  return {
246
246
  session: {
247
247
  authenticated: true,
248
- userId: 7
248
+ userId: "7"
249
249
  },
250
250
  profile: {
251
251
  displayName: "Ada Lovelace",
@@ -260,10 +260,10 @@ test("bootstrap placement runtime writes user/workspace/permissions into placeme
260
260
  }
261
261
  },
262
262
  pendingInvites: [
263
- { id: 1, workspaceId: 1, token: "a" },
264
- { id: 2, workspaceId: 2, token: "b" }
263
+ { id: "1", workspaceId: "1", token: "a" },
264
+ { id: "2", workspaceId: "2", token: "b" }
265
265
  ],
266
- workspaces: [{ id: 1, slug: "acme", name: "Acme Workspace" }],
266
+ workspaces: [{ id: "1", slug: "acme", name: "Acme Workspace" }],
267
267
  permissions: ["workspace.settings.view"]
268
268
  };
269
269
  }
@@ -280,7 +280,7 @@ test("bootstrap placement runtime writes user/workspace/permissions into placeme
280
280
  assert.equal(runtime.getWorkspaceBootstrapStatus("acme"), WORKSPACE_BOOTSTRAP_STATUS_RESOLVED);
281
281
  assert.equal(context.workspaceBootstrapStatuses?.acme, WORKSPACE_BOOTSTRAP_STATUS_RESOLVED);
282
282
  assert.deepEqual(context.user, {
283
- id: 7,
283
+ id: "7",
284
284
  displayName: "Ada Lovelace",
285
285
  name: "Ada Lovelace",
286
286
  email: "ada@example.com",
@@ -305,7 +305,7 @@ test("bootstrap placement runtime resolves workspace slug from pathname when sur
305
305
  return {
306
306
  session: {
307
307
  authenticated: true,
308
- userId: 1
308
+ userId: "1"
309
309
  },
310
310
  profile: {
311
311
  displayName: "User",
@@ -314,7 +314,7 @@ test("bootstrap placement runtime resolves workspace slug from pathname when sur
314
314
  effectiveUrl: ""
315
315
  }
316
316
  },
317
- workspaces: [{ id: 1, slug: "acme", name: "Acme Workspace" }],
317
+ workspaces: [{ id: "1", slug: "acme", name: "Acme Workspace" }],
318
318
  permissions: ["workspace.settings.view"]
319
319
  };
320
320
  }
@@ -348,7 +348,7 @@ test("bootstrap placement runtime does not mutate placement auth context", async
348
348
  return {
349
349
  session: {
350
350
  authenticated: true,
351
- userId: 9
351
+ userId: "9"
352
352
  },
353
353
  profile: {
354
354
  displayName: "User",
@@ -357,7 +357,7 @@ test("bootstrap placement runtime does not mutate placement auth context", async
357
357
  effectiveUrl: ""
358
358
  }
359
359
  },
360
- workspaces: [{ id: 1, slug: "acme", name: "Workspace" }],
360
+ workspaces: [{ id: "1", slug: "acme", name: "Workspace" }],
361
361
  permissions: []
362
362
  };
363
363
  }
@@ -387,7 +387,7 @@ test("bootstrap placement runtime refetches on route changes and users.bootstrap
387
387
  return {
388
388
  session: {
389
389
  authenticated: true,
390
- userId: 1
390
+ userId: "1"
391
391
  },
392
392
  profile: {
393
393
  displayName: "User",
@@ -396,7 +396,7 @@ test("bootstrap placement runtime refetches on route changes and users.bootstrap
396
396
  effectiveUrl: ""
397
397
  }
398
398
  },
399
- workspaces: [{ id: 1, slug: workspaceSlug || "acme", name: "Workspace" }],
399
+ workspaces: [{ id: "1", slug: workspaceSlug || "acme", name: "Workspace" }],
400
400
  permissions: []
401
401
  };
402
402
  }
@@ -436,7 +436,7 @@ test("bootstrap placement runtime refetches when auth context changes", async ()
436
436
  return {
437
437
  session: {
438
438
  authenticated: true,
439
- userId: 1
439
+ userId: "1"
440
440
  },
441
441
  profile: {
442
442
  displayName: "User",
@@ -445,7 +445,7 @@ test("bootstrap placement runtime refetches when auth context changes", async ()
445
445
  effectiveUrl: ""
446
446
  }
447
447
  },
448
- workspaces: [{ id: 1, slug: workspaceSlug || "acme", name: "Workspace" }],
448
+ workspaces: [{ id: "1", slug: workspaceSlug || "acme", name: "Workspace" }],
449
449
  permissions: []
450
450
  };
451
451
  }
@@ -535,7 +535,7 @@ test("bootstrap placement runtime reapplies theme when bootstrap payload changes
535
535
  return {
536
536
  session: {
537
537
  authenticated: true,
538
- userId: 1
538
+ userId: "1"
539
539
  },
540
540
  profile: {
541
541
  displayName: "User",
@@ -547,7 +547,7 @@ test("bootstrap placement runtime reapplies theme when bootstrap payload changes
547
547
  userSettings: {
548
548
  theme: fetchCount === 1 ? "dark" : "light"
549
549
  },
550
- workspaces: [{ id: 1, slug: workspaceSlug || "acme", name: "Workspace" }],
550
+ workspaces: [{ id: "1", slug: workspaceSlug || "acme", name: "Workspace" }],
551
551
  permissions: []
552
552
  };
553
553
  }
@@ -575,7 +575,7 @@ test("bootstrap placement runtime applies workspace palette via Vuetify workspac
575
575
  return {
576
576
  session: {
577
577
  authenticated: true,
578
- userId: 1
578
+ userId: "1"
579
579
  },
580
580
  workspaceSettings: {
581
581
  lightPrimaryColor: "#CC3344",
@@ -589,7 +589,7 @@ test("bootstrap placement runtime applies workspace palette via Vuetify workspac
589
589
  },
590
590
  workspaces: [
591
591
  {
592
- id: 1,
592
+ id: "1",
593
593
  slug: "acme",
594
594
  name: "Acme Workspace"
595
595
  }
@@ -630,8 +630,8 @@ test("bootstrap placement runtime marks workspace slug as not_found and clears w
630
630
  const placementRuntime = createPlacementRuntimeStub();
631
631
  placementRuntime.setContext(
632
632
  {
633
- workspace: { id: 1, slug: "acme", name: "Acme Workspace" },
634
- workspaces: [{ id: 1, slug: "acme", name: "Acme Workspace" }],
633
+ workspace: { id: "1", slug: "acme", name: "Acme Workspace" },
634
+ workspaces: [{ id: "1", slug: "acme", name: "Acme Workspace" }],
635
635
  permissions: ["workspace.settings.view"]
636
636
  },
637
637
  { source: "test.seed" }
@@ -680,7 +680,7 @@ test("bootstrap placement runtime updates status per workspace slug across route
680
680
  return {
681
681
  session: {
682
682
  authenticated: true,
683
- userId: 1
683
+ userId: "1"
684
684
  },
685
685
  profile: {
686
686
  displayName: "User",
@@ -689,7 +689,7 @@ test("bootstrap placement runtime updates status per workspace slug across route
689
689
  effectiveUrl: ""
690
690
  }
691
691
  },
692
- workspaces: [{ id: 1, slug: workspaceSlug || "acme", name: "Workspace" }],
692
+ workspaces: [{ id: "1", slug: workspaceSlug || "acme", name: "Workspace" }],
693
693
  permissions: []
694
694
  };
695
695
  }
@@ -721,7 +721,7 @@ test("bootstrap placement runtime uses requestedWorkspace status and keeps globa
721
721
  return {
722
722
  session: {
723
723
  authenticated: true,
724
- userId: 4
724
+ userId: "4"
725
725
  },
726
726
  profile: {
727
727
  displayName: "Chiara",
@@ -730,7 +730,7 @@ test("bootstrap placement runtime uses requestedWorkspace status and keeps globa
730
730
  effectiveUrl: ""
731
731
  }
732
732
  },
733
- workspaces: [{ id: 3, slug: "chiaramobily", name: "Chiara Workspace" }],
733
+ workspaces: [{ id: "3", slug: "chiaramobily", name: "Chiara Workspace" }],
734
734
  requestedWorkspace: {
735
735
  slug: "tonymobily",
736
736
  status: "forbidden"
@@ -763,7 +763,7 @@ test("bootstrap placement runtime uses requestedWorkspace=not_found without forc
763
763
  return {
764
764
  session: {
765
765
  authenticated: true,
766
- userId: 1
766
+ userId: "1"
767
767
  },
768
768
  profile: {
769
769
  displayName: "User",
@@ -772,7 +772,7 @@ test("bootstrap placement runtime uses requestedWorkspace=not_found without forc
772
772
  effectiveUrl: ""
773
773
  }
774
774
  },
775
- workspaces: [{ id: 1, slug: "acme", name: "Acme Workspace" }],
775
+ workspaces: [{ id: "1", slug: "acme", name: "Acme Workspace" }],
776
776
  requestedWorkspace: {
777
777
  slug: "missing",
778
778
  status: "not_found"
@@ -811,9 +811,9 @@ test("bootstrap placement runtime guard wrapper preserves delegated deny outcome
811
811
  return {
812
812
  session: {
813
813
  authenticated: true,
814
- userId: 1
814
+ userId: "1"
815
815
  },
816
- workspaces: [{ id: 1, slug: "acme", name: "Acme" }],
816
+ workspaces: [{ id: "1", slug: "acme", name: "Acme" }],
817
817
  permissions: []
818
818
  };
819
819
  }
@@ -854,7 +854,7 @@ test("bootstrap placement runtime guard wrapper blocks forbidden workspace route
854
854
  return {
855
855
  session: {
856
856
  authenticated: true,
857
- userId: 1
857
+ userId: "1"
858
858
  },
859
859
  workspaces: [],
860
860
  permissions: []
@@ -1032,7 +1032,7 @@ test("bootstrap placement runtime enforces surface access policies after bootstr
1032
1032
  return {
1033
1033
  session: {
1034
1034
  authenticated: true,
1035
- userId: 1
1035
+ userId: "1"
1036
1036
  },
1037
1037
  workspaces: [],
1038
1038
  permissions: [],
@@ -1059,9 +1059,9 @@ test("bootstrap placement runtime captures guard evaluator assignments after ini
1059
1059
  return {
1060
1060
  session: {
1061
1061
  authenticated: true,
1062
- userId: 1
1062
+ userId: "1"
1063
1063
  },
1064
- workspaces: [{ id: 1, slug: "acme", name: "Acme" }],
1064
+ workspaces: [{ id: "1", slug: "acme", name: "Acme" }],
1065
1065
  permissions: []
1066
1066
  };
1067
1067
  }