@rytass/bpm-core-nestjs-module 0.1.6 → 0.1.7

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 (3) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/README.md +135 -4
  3. package/package.json +2 -2
package/CHANGELOG.md CHANGED
@@ -9,6 +9,39 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
9
9
  Releases are managed by [`nx release`](https://nx.dev/recipes/nx-release) with
10
10
  Conventional Commits — see `nx.json` for the release config.
11
11
 
12
+ ## 0.1.7 — 2026-05-28
13
+
14
+ ### Documentation
15
+
16
+ - **README "Bring-your-own-host-auth" recipe** added under Auth
17
+ Context. Documents the `ApiMember` JSON shape, the expected status
18
+ codes for `/auth/login`, `/auth/me`, `/auth/logout`, and two
19
+ integration patterns: (A) extend the host API with `/auth/*`
20
+ controllers that emit `ApiMember` (recommended for member-base
21
+ hosts), or (B) run a thin wrapper host on a separate subdomain.
22
+ - **README "Sharing a Postgres cluster with the host"** added under
23
+ Database Setup. Compares same-schema / separate-schema / separate-DB
24
+ trade-offs, recommends separate-schema, clarifies Vault path
25
+ isolation between host and BPM.
26
+ - **`commitOrgUnitTreeDraft` signature corrected** in the Worked
27
+ Example listing — previously `{ baseUpdatedAt, draft }` (incorrect),
28
+ now `{ moves: { id, parentId, baseUpdatedAt }[] }` matching the
29
+ actual export.
30
+ - **`OrgUnitType` literal case corrected** in the README's example
31
+ comment — previously `'COMPANY' | 'DIVISION' | ...`, now `'company'
32
+ | 'division' | ...`. (See `@rytass/bpm-core-shared@0.1.7` for the
33
+ actual TS type change.)
34
+ - **"Positions are intentionally not deletable" note** added to the
35
+ Organization mirror pattern. Explains why no `deletePosition`
36
+ mutation exists and how to retire a position safely.
37
+ - **README "Package Status" current-version line refreshed** to
38
+ `0.1.7`.
39
+
40
+ ### Why a patch
41
+
42
+ Documentation-only. No public API change beyond what
43
+ `@rytass/bpm-core-shared@0.1.7` ships.
44
+
12
45
  ## 0.1.6 — 2026-05-28
13
46
 
14
47
  ### Added
package/README.md CHANGED
@@ -14,7 +14,7 @@ directory integration, storage adapters, and deployment.
14
14
 
15
15
  ## Package Status
16
16
 
17
- Current version: `0.0.1`
17
+ Current version: `0.1.7`
18
18
 
19
19
  The package is intended for NestJS backend hosts. It does not include the
20
20
  Next.js backoffice UI and does not provide a production auth system by itself.
@@ -198,6 +198,104 @@ class HostResolver {
198
198
  }
199
199
  ```
200
200
 
201
+ ### Bring-your-own-host-auth (for hosts that already have auth)
202
+
203
+ The BPM React `<AuthProvider>` calls `POST /auth/login`, `GET /auth/me`,
204
+ and `POST /auth/logout` on the BPM API host and expects responses that
205
+ match the `ApiMember` shape (exported from `@rytass/bpm-core-client`):
206
+
207
+ ```ts
208
+ interface ApiMember {
209
+ readonly memberId: string;
210
+ readonly email: string;
211
+ readonly name: string;
212
+ readonly roles: readonly string[];
213
+ readonly permissions: readonly string[];
214
+ readonly expiresAt: string; // ISO 8601
215
+ }
216
+ ```
217
+
218
+ Status-code contract:
219
+
220
+ | Endpoint | Success | Failure |
221
+ | --------------------- | ----------- | ----------------------------------- |
222
+ | `POST /auth/login` | `200` + `ApiMember` JSON | `401` (bad credentials) / `400` (validation) |
223
+ | `GET /auth/me` | `200` + `ApiMember` JSON | `401` (no session — React client treats as anonymous, not error) |
224
+ | `POST /auth/logout` | `204` (or `200` empty) | `401` ignored |
225
+
226
+ Session lifetime is owned by the host. The React `<AuthProvider>`
227
+ relies on `credentials: 'include'` plus an HTTP-only cookie issued by
228
+ the host on successful login — BPM does **not** issue cookies itself.
229
+
230
+ Hosts that already have their own JWT / cookie auth have two integration
231
+ patterns:
232
+
233
+ #### Pattern A — Extend the host API surface
234
+
235
+ Add `/auth/login`, `/auth/me`, `/auth/logout` controllers to your
236
+ existing NestJS host. Internally those endpoints call the host's
237
+ existing auth service (e.g. `MemberBaseService.login()`) and emit the
238
+ `ApiMember` shape. This is the simplest integration when BPM and the
239
+ host run on the same origin. Example for a member-base host:
240
+
241
+ ```ts
242
+ @Controller('auth')
243
+ export class HostAuthController {
244
+ constructor(private readonly memberBase: MemberBaseService) {}
245
+
246
+ @Post('login')
247
+ async login(@Body() input: LoginInput, @Res({ passthrough: true }) res: Response): Promise<ApiMember> {
248
+ const { member, accessToken, refreshToken } = await this.memberBase.login(input);
249
+ // Set the host's existing HTTP-only cookies; the React client will
250
+ // forward them on subsequent /auth/me and /graphql calls.
251
+ res.cookie('access_token', accessToken, { httpOnly: true, sameSite: 'lax' });
252
+ res.cookie('refresh_token', refreshToken, { httpOnly: true, sameSite: 'lax' });
253
+ return projectMemberToApiMember(member);
254
+ }
255
+
256
+ @Get('me')
257
+ async me(@Req() req: Request): Promise<ApiMember> {
258
+ const member = await this.memberBase.resolveCurrentMember(req); // throws 401 if no cookie
259
+ return projectMemberToApiMember(member);
260
+ }
261
+
262
+ @Post('logout')
263
+ @HttpCode(204)
264
+ logout(@Res({ passthrough: true }) res: Response): void {
265
+ res.clearCookie('access_token');
266
+ res.clearCookie('refresh_token');
267
+ }
268
+ }
269
+ ```
270
+
271
+ The `projectMemberToApiMember` helper is host-specific — map your own
272
+ member shape to BPM's expectations, project Casbin roles into the BPM
273
+ role-literal strings (see "Mapping from a host RBAC system" below),
274
+ and compute `expiresAt` from the token TTL.
275
+
276
+ #### Pattern B — Run BPM on a separate auth host
277
+
278
+ When BPM lives on its own subdomain (e.g. `bpm.example.com`) separate
279
+ from the main app at `app.example.com`, you can run a thin wrapper
280
+ host that **only** owns the `/auth/*` endpoints and a session cookie
281
+ scoped to `*.example.com`. The BPM React client points at this
282
+ subdomain via `NEXT_PUBLIC_API_URL` / `NEXT_PUBLIC_API_AUTH_URL`
283
+ (or `configureBPMClient` for server-side). The wrapper host
284
+ internally calls back to the main app to verify the user.
285
+
286
+ This pattern is heavier (cross-origin cookies, CORS) but lets the main
287
+ app stay completely decoupled from BPM's auth surface. Use it only
288
+ when a same-origin deployment is impossible.
289
+
290
+ #### Cross-host auth precedence
291
+
292
+ If the consumer wants the React client to skip the BPM auth entirely
293
+ and rely on the host's existing session check at the Next.js layer,
294
+ the simplest path is to **not mount `<AuthProvider>` from
295
+ `@rytass/bpm-core-react`** and instead implement a host-side
296
+ `useAuth()` shim that returns the host's session. This is rare — most
297
+ consumers find Pattern A simpler.
298
+
201
299
  ## Role and Permission Contract
202
300
 
203
301
  BPM guards inspect `BPMAuthContext.roles` and `BPMAuthContext.permissions` with
@@ -388,10 +486,17 @@ and observable:
388
486
  carry an opaque `metadata` JSON object that BPM never introspects.
389
487
  Store your host's primary key here (e.g. `{ shuttleOrgUnitId: 12345 }`)
390
488
  so you can chase the back-pointer when reconciling.
391
- 3. **Soft delete via `deleteOrgUnit` / `deleteMembership`.** Deletes set
489
+ 3. **Soft delete via `deleteOrgUnit` / `deleteMembership` / `deleteManagerResolution`.** Deletes set
392
490
  `deletedAt` rather than removing rows. BPM's query layer hides
393
491
  soft-deleted rows automatically; reading `orgUnitCount()` will report
394
492
  the live count.
493
+ 4. **Positions are intentionally not deletable.** There is no
494
+ `deletePosition` mutation — positions are part of the historical
495
+ record (every signed approval task references the assignee's position
496
+ at sign time, so deleting a position would lose audit data). Retire a
497
+ position by removing all active memberships referencing it; the
498
+ position itself stays. Use `updatePosition({ id, name: 'retired-' + oldName, ... })`
499
+ if you want a UI hint that a position is no longer in use.
395
500
 
396
501
  ### Worked example: idempotent sync
397
502
 
@@ -405,7 +510,7 @@ input shapes — no `{id, input: {...}}` wrapping):
405
510
  - `createOrgUnit({ code, name, type, parentId, metadataJson })`
406
511
  - `updateOrgUnit({ id, code, name, type, parentId, metadataJson })`
407
512
  - `deleteOrgUnit(id)` (soft-delete)
408
- - `commitOrgUnitTreeDraft({ baseUpdatedAt, draft })` (transactional batch moves)
513
+ - `commitOrgUnitTreeDraft({ moves: { id, parentId, baseUpdatedAt }[] })` (transactional batch moves — each entry's `baseUpdatedAt` is the row's last-known `updatedAt`, used by BPM for optimistic-locking conflict detection)
409
514
  - `createPosition` / `updatePosition` (key on `code`)
410
515
  - `createMembership` / `updateMembership` (unique by `(memberId, orgUnitId, positionId)`)
411
516
  - `createManagerResolution` / `updateManagerResolution`
@@ -423,7 +528,7 @@ interface HostOrgUnit {
423
528
  readonly hostId: string; // e.g. ERP primary key — your host-FK
424
529
  readonly code: string; // stable across sync runs
425
530
  readonly name: string;
426
- readonly type: OrgUnitType; // 'COMPANY' | 'DIVISION' | 'DEPARTMENT' | 'TEAM'
531
+ readonly type: OrgUnitType; // 'company' | 'division' | 'department' | 'team'
427
532
  readonly parentCode: string | null;
428
533
  }
429
534
 
@@ -653,6 +758,32 @@ Required environment variables for direct Vault loading:
653
758
  | `VAULT_PASSWORD` | Vault userpass password. |
654
759
  | `VAULT_PATH` | Vault KV path, defaults to develop. |
655
760
 
761
+ ### Sharing a Postgres cluster with the host
762
+
763
+ Hosts that already run their own Postgres (and their own Vault path,
764
+ e.g. `shuttle/develop`) have three options for where BPM's 22 tables
765
+ live. Pick the one that matches your operational model:
766
+
767
+ | Option | When to use | Trade-off |
768
+ |---|---|---|
769
+ | **Same database, same schema (`public`)** | Tiny prototypes, monorepo dev | Table-name collision risk — BPM owns `org_units`, `positions`, etc., common names. Audit your host's tables before choosing. |
770
+ | **Same database, separate schema** (recommended) | Most production hosts | Clean isolation; pg_dump per-schema; one connection pool. Set `DB_SCHEMA=bpm_core` in BPM's Vault path. Host stays on its own schema (typically `public`). |
771
+ | **Separate database** | Hard multi-tenant or compliance boundary | Two pools; cross-schema joins impossible (BPM doesn't need them); migration runners stay completely independent. |
772
+
773
+ For option B (separate schema), the host's own TypeORM `forRoot()` and
774
+ BPM's `buildTypeOrmModuleOptions` create **two distinct connections**
775
+ to the same Postgres cluster with different `schema:` settings — that
776
+ is the cleanest separation Nest's TypeORM module supports.
777
+
778
+ Vault paths can be split independently: keep `shuttle/develop` for the
779
+ host and create `bpm_core/develop` for BPM secrets. BPM's helper reads
780
+ its own path; the host's auth module reads its own. They do not
781
+ interfere.
782
+
783
+ > **Schema migrations** are per-schema. Run BPM's migrations against
784
+ > the schema named in `DB_SCHEMA`; the host's migrations stay on its
785
+ > own schema. There is no cross-schema migration coordination.
786
+
656
787
  ## Migrations
657
788
 
658
789
  This package ships TypeORM migrations under `src/lib/migrations`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rytass/bpm-core-nestjs-module",
3
- "version": "0.1.6",
3
+ "version": "0.1.7",
4
4
  "description": "Embeddable NestJS BPM approval workflow module: workflow engine, approval templates, forms, organization/member contracts, attachments, signatures, notifications, delegation, and TypeORM migrations.",
5
5
  "keywords": [
6
6
  "bpm",
@@ -141,7 +141,7 @@
141
141
  "@nestjs/core": "^11.0.0",
142
142
  "@nestjs/graphql": "^13.4.0",
143
143
  "@nestjs/typeorm": "^11.0.1",
144
- "@rytass/bpm-core-shared": "^0.1.6",
144
+ "@rytass/bpm-core-shared": "^0.1.7",
145
145
  "@rytass/secret-adapter-vault-nestjs": "^0.4.5 || ^0.5.0",
146
146
  "express": "^4.0.0 || ^5.0.0",
147
147
  "graphql": "^16.0.0",