@rytass/bpm-core-nestjs-module 0.1.7 → 0.1.9
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/CHANGELOG.md +47 -0
- package/README.md +144 -9
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -9,6 +9,53 @@ 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.9 — 2026-05-28
|
|
13
|
+
|
|
14
|
+
### Documentation
|
|
15
|
+
|
|
16
|
+
- **README "Machine-to-machine authentication" section** added under
|
|
17
|
+
Auth Context. Documents three patterns for server-side scripts (org
|
|
18
|
+
seeds, cron workers, integration tests) to authenticate against BPM:
|
|
19
|
+
(1) service member + login flow with cookie-jar fetch (recommended),
|
|
20
|
+
(2) service token header for hosts that issue long-lived JWTs,
|
|
21
|
+
(3) direct cookie injection for testing only.
|
|
22
|
+
- **README "Coexisting with a host's existing `<AuthProvider>`"** added
|
|
23
|
+
under Auth Context. Explains that BPM's React provider is a separate
|
|
24
|
+
context safe to nest under a host provider, with three rules of
|
|
25
|
+
thumb (scope to sub-tree, share cookie jar same-origin, don't
|
|
26
|
+
double-wrap `<AuthProvider>` directly).
|
|
27
|
+
|
|
28
|
+
### Why a patch
|
|
29
|
+
|
|
30
|
+
Documentation only.
|
|
31
|
+
|
|
32
|
+
## 0.1.8 — 2026-05-28
|
|
33
|
+
|
|
34
|
+
### Documentation
|
|
35
|
+
|
|
36
|
+
- **Casbin example BPMAuthContext shape corrected.** The 0.1.6/0.1.7
|
|
37
|
+
example returned `{ memberId, email, name, roles, permissions,
|
|
38
|
+
metadata }` — the actual `BPMAuthContext` interface is
|
|
39
|
+
`{ memberId, roles, permissions, metadata }` (no `email`, no `name`).
|
|
40
|
+
Example now stashes display data inside `metadata`, with an explicit
|
|
41
|
+
callout that name/email reach resolvers through `BPMMemberResolver`
|
|
42
|
+
instead.
|
|
43
|
+
- **`createPosition` / `updatePosition` / `createMembership` /
|
|
44
|
+
`updateMembership` / `createManagerResolution` exact signatures
|
|
45
|
+
added** to the org Worked Example section. The 0.1.7 README only
|
|
46
|
+
listed function names; downstream developers were guessing input
|
|
47
|
+
shapes (e.g. mandatory `level` and `metadataJson` on positions).
|
|
48
|
+
- **`metadataJson` write-only clarification.** The "host-FK stash"
|
|
49
|
+
rule was misleading: `metadataJson` is accepted by every mutation
|
|
50
|
+
but the records returned by `readOrganizationDashboard` do NOT
|
|
51
|
+
surface a `metadata` field. Updated to "always reconcile by `code`;
|
|
52
|
+
treat `metadataJson` as audit/debugging breadcrumb, not a live FK
|
|
53
|
+
pointer."
|
|
54
|
+
|
|
55
|
+
### Why a patch
|
|
56
|
+
|
|
57
|
+
Documentation only. No source change.
|
|
58
|
+
|
|
12
59
|
## 0.1.7 — 2026-05-28
|
|
13
60
|
|
|
14
61
|
### Documentation
|
package/README.md
CHANGED
|
@@ -296,6 +296,125 @@ the simplest path is to **not mount `<AuthProvider>` from
|
|
|
296
296
|
`useAuth()` shim that returns the host's session. This is rare — most
|
|
297
297
|
consumers find Pattern A simpler.
|
|
298
298
|
|
|
299
|
+
### Machine-to-machine authentication
|
|
300
|
+
|
|
301
|
+
Server-side scripts (org seeds, cron workers, integration tests) call
|
|
302
|
+
`configureBPMClient` to set the GraphQL base URL and optional default
|
|
303
|
+
headers, then must establish a session. Three patterns work:
|
|
304
|
+
|
|
305
|
+
#### Pattern 1 — Service member + login flow (recommended)
|
|
306
|
+
|
|
307
|
+
Create a dedicated BPM admin member (e.g. `bpm-sync@svc.example.com`),
|
|
308
|
+
store its credentials in Vault, and let the script call `loginApi`
|
|
309
|
+
during bootstrap. The login response sets an HTTP-only session cookie
|
|
310
|
+
that subsequent `requestGraphQl` calls automatically include.
|
|
311
|
+
|
|
312
|
+
```ts
|
|
313
|
+
import { configureBPMClient, loginApi } from '@rytass/bpm-core-client';
|
|
314
|
+
|
|
315
|
+
async function bootstrap(): Promise<void> {
|
|
316
|
+
configureBPMClient({
|
|
317
|
+
baseUrl: process.env.BPM_API_URL ?? 'http://localhost:17603',
|
|
318
|
+
fetch: globalThis.fetch,
|
|
319
|
+
});
|
|
320
|
+
await loginApi({
|
|
321
|
+
identifier: process.env.BPM_SYNC_IDENTIFIER!,
|
|
322
|
+
password: process.env.BPM_SYNC_PASSWORD!,
|
|
323
|
+
});
|
|
324
|
+
// Subsequent calls to readOrganizationDashboard / createOrgUnit / etc.
|
|
325
|
+
// automatically forward the session cookie.
|
|
326
|
+
}
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
Caveats: `globalThis.fetch` on Node 20+ does **not** maintain a cookie
|
|
330
|
+
jar across calls by default — set up one of:
|
|
331
|
+
|
|
332
|
+
- `undici`'s `Agent` with `cookies` enabled, then `setGlobalDispatcher(agent)`
|
|
333
|
+
- the `tough-cookie` + `node-fetch-cookies` pairing
|
|
334
|
+
- BPM's `configureBPMClient({ fetch: cookieAwareFetch })` injection
|
|
335
|
+
|
|
336
|
+
Without a cookie jar, the login succeeds but no subsequent call carries
|
|
337
|
+
the session. Most production deployments already use `undici` with
|
|
338
|
+
cookie support; verify before shipping.
|
|
339
|
+
|
|
340
|
+
#### Pattern 2 — Service token header (when supported by host)
|
|
341
|
+
|
|
342
|
+
If your wrapper host issues a long-lived service token (e.g. a JWT
|
|
343
|
+
signed with a known shared secret), pass it through
|
|
344
|
+
`configureBPMClient`'s `headers`:
|
|
345
|
+
|
|
346
|
+
```ts
|
|
347
|
+
configureBPMClient({
|
|
348
|
+
baseUrl: process.env.BPM_API_URL!,
|
|
349
|
+
headers: { Authorization: `Bearer ${process.env.BPM_SERVICE_TOKEN!}` },
|
|
350
|
+
});
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
This requires the wrapper host's auth middleware to honor `Authorization`
|
|
354
|
+
headers (member-base hosts can be configured to do so via
|
|
355
|
+
`authStrategy: 'jwt'` in addition to `cookieMode: true`).
|
|
356
|
+
|
|
357
|
+
#### Pattern 3 — Direct cookie injection (testing only)
|
|
358
|
+
|
|
359
|
+
For integration tests where you've already obtained a session cookie
|
|
360
|
+
out-of-band (e.g. from a Playwright fixture), pass it raw:
|
|
361
|
+
|
|
362
|
+
```ts
|
|
363
|
+
configureBPMClient({
|
|
364
|
+
baseUrl: 'http://localhost:17603',
|
|
365
|
+
headers: { Cookie: 'access_token=<jwt>; refresh_token=<jwt>' },
|
|
366
|
+
});
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
Do not use this in production scripts — cookies expire and the script
|
|
370
|
+
must handle refresh, which means you actually want Pattern 1.
|
|
371
|
+
|
|
372
|
+
### Coexisting with a host's existing `<AuthProvider>`
|
|
373
|
+
|
|
374
|
+
Many host applications (Shuttle, custom admin panels) already mount
|
|
375
|
+
their own `<AuthProvider>` at the root of the Next.js tree. BPM's
|
|
376
|
+
`<AuthProvider>` (mounted indirectly via `<BPMNextProviders>`) is a
|
|
377
|
+
**separate context** that only tracks BPM session state. The two do
|
|
378
|
+
not conflict — they coexist as parallel React contexts:
|
|
379
|
+
|
|
380
|
+
```tsx
|
|
381
|
+
// app/layout.tsx — host's root layout (untouched)
|
|
382
|
+
import { HostAuthProvider } from '@your-host/auth';
|
|
383
|
+
|
|
384
|
+
export default function RootLayout({ children }) {
|
|
385
|
+
return (
|
|
386
|
+
<html><body>
|
|
387
|
+
<HostAuthProvider>{children}</HostAuthProvider>
|
|
388
|
+
</body></html>
|
|
389
|
+
);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// app/operations/approval/layout.tsx — BPM sub-tree
|
|
393
|
+
'use client';
|
|
394
|
+
import { BPMNextProviders } from '@rytass/bpm-core-react/next';
|
|
395
|
+
|
|
396
|
+
export default function ApprovalLayout({ children }) {
|
|
397
|
+
return (
|
|
398
|
+
<BPMNextProviders loginPath="/login">
|
|
399
|
+
{children}
|
|
400
|
+
</BPMNextProviders>
|
|
401
|
+
);
|
|
402
|
+
}
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
Rules of thumb:
|
|
406
|
+
|
|
407
|
+
- **Scope `<BPMNextProviders>` to the BPM sub-tree** — never put it at
|
|
408
|
+
the root. Otherwise every host page goes through BPM's auth gate,
|
|
409
|
+
which redirects unauthenticated users to BPM's `/login`.
|
|
410
|
+
- **BPM auth and host auth share the cookie jar** when same-origin.
|
|
411
|
+
If `<BPMNextProviders loginPath="/login">` and the host's auth share
|
|
412
|
+
`/login`, the same login form can satisfy both — implement the host
|
|
413
|
+
login endpoint to also satisfy `/auth/login` from BPM's contract.
|
|
414
|
+
- **Do not import `<AuthProvider>` from `@rytass/bpm-core-react`
|
|
415
|
+
directly** if you've already mounted `<BPMNextProviders>` —
|
|
416
|
+
double-wrapping creates redundant `/auth/me` polls.
|
|
417
|
+
|
|
299
418
|
## Role and Permission Contract
|
|
300
419
|
|
|
301
420
|
BPM guards inspect `BPMAuthContext.roles` and `BPMAuthContext.permissions` with
|
|
@@ -353,16 +472,23 @@ export class BPMAuthContextFactory {
|
|
|
353
472
|
|
|
354
473
|
return {
|
|
355
474
|
memberId: member.id,
|
|
356
|
-
email: member.email,
|
|
357
|
-
name: member.name,
|
|
358
475
|
roles: bpmRoles,
|
|
359
476
|
permissions: [], // Host can also use `bpm:*` style permissions if preferred.
|
|
360
|
-
|
|
477
|
+
// The BPMAuthContext shape does NOT include name/email — those are
|
|
478
|
+
// surfaced through BPMMemberResolver instead. If you want them
|
|
479
|
+
// available to downstream resolvers without an extra resolver call,
|
|
480
|
+
// stash them in `metadata` (BPM passes it through unchanged).
|
|
481
|
+
metadata: { name: member.name, email: member.email },
|
|
361
482
|
};
|
|
362
483
|
}
|
|
363
484
|
}
|
|
364
485
|
```
|
|
365
486
|
|
|
487
|
+
> **`BPMAuthContext` shape**: `{ memberId, roles, permissions, metadata }`
|
|
488
|
+
> — exactly four fields. The host's `name` / `email` are not part of the
|
|
489
|
+
> contract; resolvers call `BPMMemberResolver.resolve(memberId)` when
|
|
490
|
+
> they need display data.
|
|
491
|
+
|
|
366
492
|
> The host owns the mapping table. Keep it in one place (a constant
|
|
367
493
|
> module or a database table) so adding a new BPM tier in a future
|
|
368
494
|
> BPMCore release becomes a one-line addition.
|
|
@@ -482,10 +608,15 @@ and observable:
|
|
|
482
608
|
uniqueness; use your host's stable identifier here (e.g. an LDAP DN
|
|
483
609
|
slug or a internal ERP code). On a re-sync, look up existing entities
|
|
484
610
|
by `code` before deciding INSERT vs UPDATE.
|
|
485
|
-
2. **`
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
611
|
+
2. **`metadataJson` is write-only.** The mutations
|
|
612
|
+
(`createOrgUnit`/`updateOrgUnit`/`createPosition`/`updatePosition`)
|
|
613
|
+
accept a `metadataJson` string that BPM stores verbatim. **However**,
|
|
614
|
+
`OrgUnitRecord` and `PositionRecord` returned by
|
|
615
|
+
`readOrganizationDashboard` do NOT include the metadata field —
|
|
616
|
+
it's intentionally hidden so the JSON blob doesn't bloat every
|
|
617
|
+
pagination payload. **For reconciliation, always key on `code`**
|
|
618
|
+
(which IS returned). Treat `metadataJson` as a debugging/audit
|
|
619
|
+
stash, not a live FK pointer.
|
|
489
620
|
3. **Soft delete via `deleteOrgUnit` / `deleteMembership` / `deleteManagerResolution`.** Deletes set
|
|
490
621
|
`deletedAt` rather than removing rows. BPM's query layer hides
|
|
491
622
|
soft-deleted rows automatically; reading `orgUnitCount()` will report
|
|
@@ -511,7 +642,11 @@ input shapes — no `{id, input: {...}}` wrapping):
|
|
|
511
642
|
- `updateOrgUnit({ id, code, name, type, parentId, metadataJson })`
|
|
512
643
|
- `deleteOrgUnit(id)` (soft-delete)
|
|
513
644
|
- `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)
|
|
514
|
-
- `createPosition`
|
|
645
|
+
- `createPosition({ code, name, level, metadataJson })` — `level` (1+) controls hierarchy weight (higher = more senior, used by manager-resolution priority sort); `metadataJson` is required (pass `'{}'` if unused)
|
|
646
|
+
- `updatePosition({ id, code, name, level, metadataJson })` — every field except `id` is `T | null`; pass `null` to leave a field unchanged
|
|
647
|
+
- `createMembership({ memberId, orgUnitId, positionId, isPrimary, effectiveFrom, effectiveTo })` — `positionId` and `effectiveTo` accept `null`; `effectiveFrom` is an ISO timestamp string. Uniqueness key is `(memberId, orgUnitId, positionId)`
|
|
648
|
+
- `updateMembership({ id, orgUnitId, positionId, isPrimary, effectiveFrom, effectiveTo })` — every field except `id` is `T | null`
|
|
649
|
+
- `createManagerResolution({ scopeType, scopeId, managerMemberId, priority, effectiveFrom, effectiveTo })` — `scopeType` is `'MEMBER' | 'ORG_UNIT' | 'POSITION'`; `priority` orders the resolver chain (lower number = checked first)
|
|
515
650
|
- `createMembership` / `updateMembership` (unique by `(memberId, orgUnitId, positionId)`)
|
|
516
651
|
- `createManagerResolution` / `updateManagerResolution`
|
|
517
652
|
|
|
@@ -528,7 +663,7 @@ interface HostOrgUnit {
|
|
|
528
663
|
readonly hostId: string; // e.g. ERP primary key — your host-FK
|
|
529
664
|
readonly code: string; // stable across sync runs
|
|
530
665
|
readonly name: string;
|
|
531
|
-
readonly type: OrgUnitType; // '
|
|
666
|
+
readonly type: OrgUnitType; // 'COMPANY' | 'DIVISION' | 'DEPARTMENT' | 'TEAM'
|
|
532
667
|
readonly parentCode: string | null;
|
|
533
668
|
}
|
|
534
669
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rytass/bpm-core-nestjs-module",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.9",
|
|
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.
|
|
144
|
+
"@rytass/bpm-core-shared": "^0.1.9",
|
|
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",
|