@fonderie/workspaces 1.0.0 → 1.1.0

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/dist/index.d.ts CHANGED
@@ -4,6 +4,7 @@ import { EventBus } from '@fonderie/events';
4
4
  import { IInvitation, IMember, IRole, IWorkspaceSettings, IWorkspace } from './types.js';
5
5
  export { InvitationStatus, WorkspaceType } from './types.js';
6
6
  export { requireWorkspace, withWorkspace } from './middlewares/index.js';
7
+ import { z } from 'zod';
7
8
 
8
9
  declare const MESSAGE_KEYS: {
9
10
  readonly workspaceInvitation: "workspace-invitation";
@@ -95,4 +96,76 @@ declare function toMemberDTO(m: IMember): IMemberDTO;
95
96
  declare function toInvitationDTO(inv: IInvitation): IInvitationDTO;
96
97
  declare function toSettingsDTO(s: IWorkspaceSettings): IWorkspaceSettingsDTO;
97
98
 
98
- export { EVENT_KEYS, IInvitation, type IInvitationDTO, IMember, type IMemberDTO, IRole, type IRoleDTO, IWorkspace, type IWorkspaceDTO, IWorkspaceSettings, type IWorkspaceSettingsDTO, type IWorkspacesConfig, MESSAGE_KEYS, type WorkspacesEventKey, type WorkspacesMessageKey, WorkspacesModule, toInvitationDTO, toMemberDTO, toRoleDTO, toSettingsDTO, toWorkspaceDTO };
99
+ declare const createWorkspaceSchema: z.ZodObject<{
100
+ name: z.ZodString;
101
+ description: z.ZodOptional<z.ZodString>;
102
+ type: z.ZodOptional<z.ZodString>;
103
+ }, z.core.$strip>;
104
+ declare const updateWorkspaceSchema: z.ZodObject<{
105
+ name: z.ZodOptional<z.ZodString>;
106
+ description: z.ZodOptional<z.ZodNullable<z.ZodString>>;
107
+ motto: z.ZodOptional<z.ZodNullable<z.ZodString>>;
108
+ phone: z.ZodOptional<z.ZodNullable<z.ZodString>>;
109
+ businessType: z.ZodOptional<z.ZodNullable<z.ZodString>>;
110
+ address: z.ZodOptional<z.ZodNullable<z.ZodObject<{
111
+ line1: z.ZodOptional<z.ZodString>;
112
+ line2: z.ZodOptional<z.ZodString>;
113
+ city: z.ZodOptional<z.ZodString>;
114
+ region: z.ZodOptional<z.ZodString>;
115
+ postalCode: z.ZodOptional<z.ZodString>;
116
+ country: z.ZodOptional<z.ZodString>;
117
+ }, z.core.$loose>>>;
118
+ }, z.core.$strip>;
119
+ declare const updateSettingsSchema: z.ZodObject<{
120
+ locale: z.ZodOptional<z.ZodString>;
121
+ timezone: z.ZodOptional<z.ZodString>;
122
+ currency: z.ZodOptional<z.ZodString>;
123
+ dateFormat: z.ZodOptional<z.ZodString>;
124
+ timeFormat: z.ZodOptional<z.ZodString>;
125
+ }, z.core.$strip>;
126
+ declare const addMemberRoleSchema: z.ZodObject<{
127
+ roleId: z.ZodString;
128
+ }, z.core.$strip>;
129
+ declare const createInvitationsSchema: z.ZodUnion<readonly [z.ZodObject<{
130
+ email: z.ZodPipe<z.ZodString, z.ZodEmail>;
131
+ roleId: z.ZodOptional<z.ZodString>;
132
+ }, z.core.$strip>, z.ZodArray<z.ZodObject<{
133
+ email: z.ZodPipe<z.ZodString, z.ZodEmail>;
134
+ roleId: z.ZodOptional<z.ZodString>;
135
+ }, z.core.$strip>>]>;
136
+ declare const acceptInvitationSchema: z.ZodObject<{
137
+ pin: z.ZodString;
138
+ }, z.core.$strip>;
139
+ declare const createRoleSchema: z.ZodObject<{
140
+ name: z.ZodString;
141
+ description: z.ZodOptional<z.ZodString>;
142
+ }, z.core.$strip>;
143
+ declare const updateRoleSchema: z.ZodObject<{
144
+ name: z.ZodOptional<z.ZodString>;
145
+ description: z.ZodOptional<z.ZodNullable<z.ZodString>>;
146
+ active: z.ZodOptional<z.ZodBoolean>;
147
+ }, z.core.$strip>;
148
+ declare const setRolePermissionsSchema: z.ZodObject<{
149
+ permissions: z.ZodArray<z.ZodObject<{
150
+ permissionKey: z.ZodString;
151
+ canCreate: z.ZodOptional<z.ZodBoolean>;
152
+ canRead: z.ZodOptional<z.ZodBoolean>;
153
+ canUpdate: z.ZodOptional<z.ZodBoolean>;
154
+ canDelete: z.ZodOptional<z.ZodBoolean>;
155
+ }, z.core.$strip>>;
156
+ }, z.core.$strip>;
157
+
158
+ declare const schemas_acceptInvitationSchema: typeof acceptInvitationSchema;
159
+ declare const schemas_addMemberRoleSchema: typeof addMemberRoleSchema;
160
+ declare const schemas_createInvitationsSchema: typeof createInvitationsSchema;
161
+ declare const schemas_createRoleSchema: typeof createRoleSchema;
162
+ declare const schemas_createWorkspaceSchema: typeof createWorkspaceSchema;
163
+ declare const schemas_setRolePermissionsSchema: typeof setRolePermissionsSchema;
164
+ declare const schemas_updateRoleSchema: typeof updateRoleSchema;
165
+ declare const schemas_updateSettingsSchema: typeof updateSettingsSchema;
166
+ declare const schemas_updateWorkspaceSchema: typeof updateWorkspaceSchema;
167
+ declare namespace schemas {
168
+ export { schemas_acceptInvitationSchema as acceptInvitationSchema, schemas_addMemberRoleSchema as addMemberRoleSchema, schemas_createInvitationsSchema as createInvitationsSchema, schemas_createRoleSchema as createRoleSchema, schemas_createWorkspaceSchema as createWorkspaceSchema, schemas_setRolePermissionsSchema as setRolePermissionsSchema, schemas_updateRoleSchema as updateRoleSchema, schemas_updateSettingsSchema as updateSettingsSchema, schemas_updateWorkspaceSchema as updateWorkspaceSchema };
169
+ }
170
+
171
+ export { EVENT_KEYS, IInvitation, type IInvitationDTO, IMember, type IMemberDTO, IRole, type IRoleDTO, IWorkspace, type IWorkspaceDTO, IWorkspaceSettings, type IWorkspaceSettingsDTO, type IWorkspacesConfig, MESSAGE_KEYS, type WorkspacesEventKey, type WorkspacesMessageKey, WorkspacesModule, schemas, toInvitationDTO, toMemberDTO, toRoleDTO, toSettingsDTO, toWorkspaceDTO };
package/dist/index.js CHANGED
@@ -1,3 +1,9 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __export = (target, all) => {
3
+ for (var name2 in all)
4
+ __defProp(target, name2, { get: all[name2], enumerable: true });
5
+ };
6
+
1
7
  // src/config.ts
2
8
  var MESSAGE_KEYS = {
3
9
  workspaceInvitation: "workspace-invitation"
@@ -7,7 +13,84 @@ var EVENT_KEYS = {
7
13
  };
8
14
 
9
15
  // src/routes.ts
10
- import { requireAuth } from "@fonderie/core/middlewares";
16
+ import { requireAuth, validate } from "@fonderie/core/middlewares";
17
+
18
+ // src/schemas.ts
19
+ var schemas_exports = {};
20
+ __export(schemas_exports, {
21
+ acceptInvitationSchema: () => acceptInvitationSchema,
22
+ addMemberRoleSchema: () => addMemberRoleSchema,
23
+ createInvitationsSchema: () => createInvitationsSchema,
24
+ createRoleSchema: () => createRoleSchema,
25
+ createWorkspaceSchema: () => createWorkspaceSchema,
26
+ setRolePermissionsSchema: () => setRolePermissionsSchema,
27
+ updateRoleSchema: () => updateRoleSchema,
28
+ updateSettingsSchema: () => updateSettingsSchema,
29
+ updateWorkspaceSchema: () => updateWorkspaceSchema
30
+ });
31
+ import { z } from "zod";
32
+ var name = z.string().trim().min(1, "name is required").max(200);
33
+ var email = z.string().trim().pipe(z.email());
34
+ var createWorkspaceSchema = z.object({
35
+ name,
36
+ description: z.string().max(2e3).optional(),
37
+ // PERSONAL workspaces are created automatically by the module; the
38
+ // controller enforces the rule — schema just types the field.
39
+ type: z.string().max(40).optional()
40
+ });
41
+ var addressSchema = z.object({
42
+ line1: z.string().max(200).optional(),
43
+ line2: z.string().max(200).optional(),
44
+ city: z.string().max(100).optional(),
45
+ region: z.string().max(100).optional(),
46
+ postalCode: z.string().max(20).optional(),
47
+ country: z.string().max(60).optional()
48
+ }).passthrough();
49
+ var updateWorkspaceSchema = z.object({
50
+ name: z.string().trim().min(1).max(200).optional(),
51
+ description: z.string().max(2e3).nullable().optional(),
52
+ motto: z.string().max(300).nullable().optional(),
53
+ phone: z.string().max(32).nullable().optional(),
54
+ businessType: z.string().max(100).nullable().optional(),
55
+ address: addressSchema.nullable().optional()
56
+ }).refine((o) => Object.values(o).some((v) => v !== void 0), "Provide at least one field");
57
+ var updateSettingsSchema = z.object({
58
+ locale: z.string().max(35).optional(),
59
+ timezone: z.string().max(64).optional(),
60
+ currency: z.string().max(8).optional(),
61
+ dateFormat: z.string().max(32).optional(),
62
+ timeFormat: z.string().max(32).optional()
63
+ }).refine((o) => Object.values(o).some((v) => v !== void 0), "No settings provided");
64
+ var addMemberRoleSchema = z.object({ roleId: z.string().min(1, "roleId is required") });
65
+ var inviteEntry = z.object({
66
+ email,
67
+ roleId: z.string().min(1).optional()
68
+ });
69
+ var createInvitationsSchema = z.union([
70
+ inviteEntry,
71
+ z.array(inviteEntry).min(1, "at least one invite is required")
72
+ ]);
73
+ var acceptInvitationSchema = z.object({ pin: z.string().trim().min(1, "pin is required") });
74
+ var createRoleSchema = z.object({
75
+ name,
76
+ description: z.string().max(1e3).optional()
77
+ });
78
+ var updateRoleSchema = z.object({
79
+ name: z.string().trim().min(1).max(200).optional(),
80
+ description: z.string().max(1e3).nullable().optional(),
81
+ active: z.boolean().optional()
82
+ }).refine((o) => Object.values(o).some((v) => v !== void 0), "Provide at least one field");
83
+ var setRolePermissionsSchema = z.object({
84
+ permissions: z.array(
85
+ z.object({
86
+ permissionKey: z.string().min(1),
87
+ canCreate: z.boolean().optional(),
88
+ canRead: z.boolean().optional(),
89
+ canUpdate: z.boolean().optional(),
90
+ canDelete: z.boolean().optional()
91
+ })
92
+ )
93
+ });
11
94
 
12
95
  // src/middlewares/workspace-context.ts
13
96
  import { setApiResponse, HTTP } from "@fonderie/core";
@@ -421,10 +504,10 @@ async function createRole(opts, store) {
421
504
  if (!role) throw new Error("Failed to create role");
422
505
  return role;
423
506
  }
424
- async function findSystemRole(name, store) {
507
+ async function findSystemRole(name2, store) {
425
508
  const [row] = await store.query(
426
509
  `SELECT ${SELECT_ROLE} FROM fonderie_roles WHERE name = $1 AND is_system = true LIMIT 1`,
427
- [name]
510
+ [name2]
428
511
  );
429
512
  return row ?? null;
430
513
  }
@@ -513,8 +596,8 @@ var RoleModel = class {
513
596
  create(opts) {
514
597
  return createRole(opts, this.store);
515
598
  }
516
- findSystem(name) {
517
- return findSystemRole(name, this.store);
599
+ findSystem(name2) {
600
+ return findSystemRole(name2, this.store);
518
601
  }
519
602
  findById(id) {
520
603
  return getRoleById(id, this.store);
@@ -621,10 +704,10 @@ function workspaceController(store, config) {
621
704
  },
622
705
  async create(ctx) {
623
706
  const body = ctx.meta["body"];
624
- const name = body?.["name"];
707
+ const name2 = body?.["name"];
625
708
  const description = body?.["description"];
626
709
  const type = body?.["type"];
627
- if (typeof name !== "string" || name.trim().length === 0) {
710
+ if (typeof name2 !== "string" || name2.trim().length === 0) {
628
711
  return setApiResponse2(HTTP2.UNPROCESSABLE, "INVALID_PARAMETER", "name is required");
629
712
  }
630
713
  if (type === "PERSONAL") {
@@ -634,10 +717,10 @@ function workspaceController(store, config) {
634
717
  "Personal workspaces are created automatically"
635
718
  );
636
719
  }
637
- const slug = name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
720
+ const slug = name2.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
638
721
  const workspace = await store.transaction(async (tx) => {
639
722
  const wsOpts = {
640
- name: name.trim(),
723
+ name: name2.trim(),
641
724
  slug,
642
725
  ownerId: ctx.user.id,
643
726
  type: typeof type === "string" ? type : "ORGANIZATION"
@@ -676,7 +759,7 @@ function workspaceController(store, config) {
676
759
  if (body?.["businessType"] !== void 0)
677
760
  opts.businessType = typeof body["businessType"] === "string" ? body["businessType"] : null;
678
761
  if (body?.["address"] !== void 0 && typeof body["address"] === "object")
679
- opts.address = body["address"];
762
+ opts.address = body["address"] ?? null;
680
763
  const workspace = await workspaces.update(ctx.workspace.id, opts);
681
764
  if (!workspace) return setApiResponse2(HTTP2.NOT_FOUND, "NOT_FOUND", "Workspace not found");
682
765
  return setApiResponse2(HTTP2.OK, "WORKSPACE_UPDATED", "Workspace updated successfully.", {
@@ -820,14 +903,14 @@ function roleController(store) {
820
903
  return setApiResponse4(HTTP4.NOT_FOUND, "NOT_FOUND", "Workspace not found");
821
904
  }
822
905
  const body = ctx.meta["body"];
823
- const name = body?.["name"];
906
+ const name2 = body?.["name"];
824
907
  const description = body?.["description"];
825
- if (typeof name !== "string" || name.trim().length === 0) {
908
+ if (typeof name2 !== "string" || name2.trim().length === 0) {
826
909
  return setApiResponse4(HTTP4.UNPROCESSABLE, "INVALID_PARAMETER", "name is required");
827
910
  }
828
911
  try {
829
912
  const opts = {
830
- name: name.trim(),
913
+ name: name2.trim(),
831
914
  workspaceId: ctx.workspace.id
832
915
  };
833
916
  if (typeof description === "string") {
@@ -1132,21 +1215,21 @@ function invitationController(store, ttl, bus) {
1132
1215
  }
1133
1216
  const results = await Promise.all(
1134
1217
  entries.map(async (entry) => {
1135
- const email = entry["email"];
1218
+ const email2 = entry["email"];
1136
1219
  const resolvedRoleId = entry["roleId"] ?? defaultRoleId;
1137
1220
  const invitation = await invitations.create({
1138
1221
  workspaceId: ctx.workspace.id,
1139
- email,
1222
+ email: email2,
1140
1223
  roleId: resolvedRoleId,
1141
1224
  ttl
1142
1225
  });
1143
1226
  bus?.emit(NOTIFICATION_EVENT, {
1144
1227
  type: MESSAGE_KEYS.workspaceInvitation,
1145
- recipient: { email, phone: null, deviceToken: null },
1228
+ recipient: { email: email2, phone: null, deviceToken: null },
1146
1229
  data: { token: invitation.token, pin: invitation.pin }
1147
1230
  }).catch(() => {
1148
1231
  });
1149
- return { invitationId: invitation.id, email };
1232
+ return { invitationId: invitation.id, email: email2 };
1150
1233
  })
1151
1234
  );
1152
1235
  return setApiResponse5(HTTP5.CREATED, "INVITATIONS_SENT", "Invitations sent successfully.", {
@@ -1191,36 +1274,36 @@ function buildWorkspaceRoutes(store, config, bus) {
1191
1274
  const invitation = invitationController(store, ttl, bus);
1192
1275
  return [
1193
1276
  // ── Workspace creation + listing (no workspace context required)
1194
- ["POST", "/workspaces", requireAuth, workspace.create],
1277
+ ["POST", "/workspaces", requireAuth, validate(createWorkspaceSchema), workspace.create],
1195
1278
  ["GET", "/workspaces", requireAuth, workspace.list],
1196
1279
  // ── Members (workspace resolved from X-Workspace-ID header)
1197
1280
  ["GET", "/workspaces/members", requireAuth, wsCtx, member.list],
1198
1281
  ["DELETE", "/workspaces/members/:userId", requireAuth, wsCtx, member.remove],
1199
1282
  ["GET", "/workspaces/members/:userId/roles", requireAuth, wsCtx, member.getUserRoles],
1200
- ["POST", "/workspaces/members/:userId/roles", requireAuth, wsCtx, member.addRole],
1283
+ ["POST", "/workspaces/members/:userId/roles", requireAuth, wsCtx, validate(addMemberRoleSchema), member.addRole],
1201
1284
  ["DELETE", "/workspaces/members/:userId/roles/:roleId", requireAuth, wsCtx, member.removeRole],
1202
1285
  // ── Invitations
1203
1286
  ["GET", "/workspaces/invitations", requireAuth, wsCtx, invitation.list],
1204
- ["POST", "/workspaces/invitations", requireAuth, wsCtx, invitation.invite],
1287
+ ["POST", "/workspaces/invitations", requireAuth, wsCtx, validate(createInvitationsSchema), invitation.invite],
1205
1288
  ["DELETE", "/workspaces/invitations/:inviteId", requireAuth, wsCtx, invitation.cancel],
1206
- ["POST", "/workspaces/invitations/accept", requireAuth, invitation.accept],
1289
+ ["POST", "/workspaces/invitations/accept", requireAuth, validate(acceptInvitationSchema), invitation.accept],
1207
1290
  // ── Roles
1208
- ["POST", "/workspaces/roles", requireAuth, wsCtx, role.create],
1291
+ ["POST", "/workspaces/roles", requireAuth, wsCtx, validate(createRoleSchema), role.create],
1209
1292
  ["GET", "/workspaces/roles", requireAuth, wsCtx, role.list],
1210
1293
  ["GET", "/workspaces/roles/:roleId", requireAuth, wsCtx, role.get],
1211
- ["PUT", "/workspaces/roles/:roleId", requireAuth, wsCtx, role.update],
1294
+ ["PUT", "/workspaces/roles/:roleId", requireAuth, wsCtx, validate(updateRoleSchema), role.update],
1212
1295
  ["DELETE", "/workspaces/roles/:roleId", requireAuth, wsCtx, role.remove],
1213
- ["POST", "/workspaces/roles/:roleId/permissions", requireAuth, wsCtx, role.setPermissions],
1296
+ ["POST", "/workspaces/roles/:roleId/permissions", requireAuth, wsCtx, validate(setRolePermissionsSchema), role.setPermissions],
1214
1297
  // ── Workspace lifecycle
1215
1298
  ["POST", "/workspaces/archive", requireAuth, wsCtx, workspace.archive],
1216
1299
  ["POST", "/workspaces/restore", requireAuth, wsCtx, workspace.restore],
1217
1300
  ["GET", "/workspaces/settings", requireAuth, wsCtx, workspace.getSettings],
1218
- ["PUT", "/workspaces/settings", requireAuth, wsCtx, workspace.updateSettings],
1301
+ ["PUT", "/workspaces/settings", requireAuth, wsCtx, validate(updateSettingsSchema), workspace.updateSettings],
1219
1302
  // ── Path-based lookup by ID (admin / cross-workspace use)
1220
1303
  ["GET", "/workspaces/:id", requireAuth, wsCtx, workspace.get],
1221
1304
  // ── Update current workspace — ID resolved from X-Workspace-ID header
1222
1305
  // (or personal workspace fallback when header is absent)
1223
- ["PUT", "/workspaces", requireAuth, wsCtx, workspace.update]
1306
+ ["PUT", "/workspaces", requireAuth, wsCtx, validate(updateWorkspaceSchema), workspace.update]
1224
1307
  ];
1225
1308
  }
1226
1309
 
@@ -1251,14 +1334,14 @@ var WorkspacesModule = class {
1251
1334
  }
1252
1335
  }
1253
1336
  async provisionPersonalWorkspace(payload) {
1254
- const name = "My Workspace";
1337
+ const name2 = "My Workspace";
1255
1338
  const slug = `${payload.userId}-personal`;
1256
1339
  let workspaceId;
1257
1340
  await this.store.transaction(async (tx) => {
1258
1341
  const wsModel = new WorkspaceModel(tx);
1259
1342
  const roleModel = new RoleModel(tx);
1260
1343
  const memModel = new MemberModel(tx);
1261
- const ws = await wsModel.createPersonal({ name, slug, ownerId: payload.userId });
1344
+ const ws = await wsModel.createPersonal({ name: name2, slug, ownerId: payload.userId });
1262
1345
  if (!ws) return;
1263
1346
  workspaceId = ws.id;
1264
1347
  const adminRole = await roleModel.findSystem("ADMIN");
@@ -1293,6 +1376,7 @@ export {
1293
1376
  MESSAGE_KEYS,
1294
1377
  WorkspacesModule,
1295
1378
  requireWorkspace,
1379
+ schemas_exports as schemas,
1296
1380
  toInvitationDTO,
1297
1381
  toMemberDTO,
1298
1382
  toRoleDTO,