@rytass/wms-module-graphql 0.3.0 → 0.5.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/README.md +55 -0
- package/adapters/event-emitter.d.ts +29 -0
- package/adapters/event-emitter.js +41 -0
- package/adapters/member-base.d.ts +35 -0
- package/adapters/member-base.js +47 -0
- package/docs/README.md +6 -0
- package/docs/api-reference.md +100 -1
- package/docs/authorization.md +349 -0
- package/docs/events.md +304 -0
- package/docs/integration-guide.md +121 -4
- package/index.cjs.js +299 -3
- package/index.d.ts +4 -0
- package/index.js +7 -0
- package/lib/auth/host-skip.constant.d.ts +27 -0
- package/lib/auth/host-skip.constant.js +29 -0
- package/lib/auth/index.d.ts +4 -0
- package/lib/auth/wms-auth.guard.d.ts +19 -0
- package/lib/auth/wms-auth.guard.js +41 -0
- package/lib/auth/wms-auth.types.d.ts +36 -0
- package/lib/auth/wms-auth.types.js +6 -0
- package/lib/auth/wms-operation.decorator.d.ts +23 -0
- package/lib/auth/wms-operation.decorator.js +29 -0
- package/lib/events/index.d.ts +3 -0
- package/lib/events/wms-mutation-event.constants.d.ts +12 -0
- package/lib/events/wms-mutation-event.constants.js +4 -0
- package/lib/events/wms-mutation-event.decorator.d.ts +15 -0
- package/lib/events/wms-mutation-event.decorator.js +20 -0
- package/lib/events/wms-mutation.interceptor.d.ts +19 -0
- package/lib/events/wms-mutation.interceptor.js +72 -0
- package/lib/graphql.module.d.ts +20 -3
- package/lib/graphql.module.js +19 -4
- package/lib/mutations/allocate-inventory-order.mutation.js +10 -0
- package/lib/mutations/hold-inventory-order.mutation.js +10 -0
- package/lib/mutations/loader.mutation.js +14 -0
- package/lib/mutations/location.mutation.js +8 -0
- package/lib/mutations/material.mutation.js +8 -0
- package/lib/mutations/merge-batch.mutation.js +6 -0
- package/lib/mutations/quality-inspection-order.mutation.js +6 -0
- package/lib/mutations/receive-inventory-order.mutation.js +10 -0
- package/lib/mutations/reclassify-inventory-order.mutation.js +6 -0
- package/lib/mutations/scrape-order.mutation.js +6 -0
- package/lib/mutations/ship-inventory-order.mutation.js +12 -0
- package/lib/mutations/split-batch.mutation.js +6 -0
- package/lib/mutations/transfer-customer-order.mutation.js +10 -0
- package/lib/mutations/transfer-order.mutation.js +12 -0
- package/lib/mutations/transfer-vendor-order.mutation.js +10 -0
- package/lib/mutations/warehouse-map.mutation.js +6 -0
- package/lib/queries/allocate-inventory-order.query.js +3 -0
- package/lib/queries/batch.query.js +3 -0
- package/lib/queries/hold-inventory-order.query.js +3 -0
- package/lib/queries/loader.query.js +8 -0
- package/lib/queries/location.query.js +4 -0
- package/lib/queries/material.query.js +4 -0
- package/lib/queries/recieve-inventory-order.query.js +4 -0
- package/lib/queries/sale-order.query.js +4 -0
- package/lib/queries/ship-inventory-order.query.js +4 -0
- package/lib/queries/stock.query.js +7 -0
- package/lib/queries/transfer-order.query.js +4 -0
- package/lib/queries/unit.query.js +3 -0
- package/lib/queries/warehouse-map.query.js +3 -0
- package/package.json +17 -3
|
@@ -16,7 +16,9 @@ Target audience: engineers integrating WMS into a new product who want a working
|
|
|
16
16
|
5. [Frontend setup (Next.js Pages Router)](#5-frontend-setup-nextjs-pages-router)
|
|
17
17
|
6. [Pages Router shell pattern (the 19 thin shells)](#6-pages-router-shell-pattern-the-19-thin-shells)
|
|
18
18
|
7. [Apollo client wiring](#7-apollo-client-wiring)
|
|
19
|
-
8. [Authentication
|
|
19
|
+
8. [Authentication, authorization & events](#8-authentication-authorization--events)
|
|
20
|
+
- [8.5 Backend authorization (resolver-level)](#85-backend-authorization-resolver-level)
|
|
21
|
+
- [8.6 Mutation events & hooks](#86-mutation-events--hooks)
|
|
20
22
|
9. [Database support matrix](#9-database-support-matrix)
|
|
21
23
|
10. [Seed data for demos](#10-seed-data-for-demos)
|
|
22
24
|
11. [Deployment notes](#11-deployment-notes)
|
|
@@ -739,9 +741,9 @@ The full list of generated `Document` constants and their types is in
|
|
|
739
741
|
|
|
740
742
|
---
|
|
741
743
|
|
|
742
|
-
## 8. Authentication
|
|
744
|
+
## 8. Authentication, authorization & events
|
|
743
745
|
|
|
744
|
-
The lib
|
|
746
|
+
The lib ships **no frontend auth** — the page components assume they render in an authenticated context and call queries on mount without checking session state. On the **backend**, `@rytass/wms-module-graphql` ships an *opt-in, host-neutral authorization protocol* you wire to your own permission system (member-base / Casbin / JWT / anything). See [§8.5 Backend authorization](#85-backend-authorization-resolver-level).
|
|
745
747
|
|
|
746
748
|
### 8.1 Stub for demos
|
|
747
749
|
|
|
@@ -829,7 +831,122 @@ Replace the `AuthGuard` stub with your own logic that:
|
|
|
829
831
|
2. Provides member identity to GraphQL via the Apollo client's `context` link.
|
|
830
832
|
3. Handles 401 responses (logout / redirect).
|
|
831
833
|
|
|
832
|
-
|
|
834
|
+
### 8.5 Backend authorization (resolver-level)
|
|
835
|
+
|
|
836
|
+
> 📖 This is the condensed version. The full design — execution order, member-base / custom-JWT / no-guard recipes, the single coupling point, the exported API, and a troubleshooting FAQ — lives in [**`docs/authorization.md`**](./authorization.md).
|
|
837
|
+
|
|
838
|
+
Every WMS root `@Query` / `@Mutation` is annotated with `@WmsOperation({ resource, action })` — a host-neutral permission requirement. The library enforces it through its own route-level `WmsAuthGuard`, which delegates the actual allow/deny decision to an `authChecker` **you** supply at module registration. The library never imports your auth system; you map WMS's `resource`/`action` onto whatever your system understands.
|
|
839
|
+
|
|
840
|
+
> ⚠️ **The global-guard pitfall.** A deny-by-default global guard — e.g. the `CasbinGuard` that `@rytass/member-base-nestjs-module` registers as `APP_GUARD` — blankets *every* resolver in the compiled schema, including WMS's. Because WMS resolvers carry no host-specific permission metadata, such a guard rejects them all ("every WMS query/mutation is forbidden"). WMS solves this by stamping each operation with a host-skip marker (default: member-base's `IS_ROUTE_PUBLIC`) so the global guard defers, while `WmsAuthGuard` does the real enforcement. **You do not need to touch your global guard.**
|
|
841
|
+
|
|
842
|
+
#### Wire it up (member-base / Casbin)
|
|
843
|
+
|
|
844
|
+
```ts
|
|
845
|
+
// app.module.ts
|
|
846
|
+
import { WmsModuleGraphqlModule } from '@rytass/wms-module-graphql';
|
|
847
|
+
import { createMemberBaseWmsAuthChecker } from '@rytass/wms-module-graphql/adapters/member-base';
|
|
848
|
+
|
|
849
|
+
WmsModuleGraphqlModule.forRootAsync({
|
|
850
|
+
// ...existing core options (defaultLoaderSerialId, useFactory, etc.)...
|
|
851
|
+
authChecker: createMemberBaseWmsAuthChecker({
|
|
852
|
+
// Map WMS resource/action onto your Casbin [subject, action] tuples.
|
|
853
|
+
// Return multiple tuples to mean "allow if ANY passes" (OR semantics).
|
|
854
|
+
map: ({ resource, action }) => [[`wms:${resource}`, action]],
|
|
855
|
+
// e.g. material:read -> enforce(memberId, domain, 'wms:material', 'read')
|
|
856
|
+
}),
|
|
857
|
+
});
|
|
858
|
+
```
|
|
859
|
+
|
|
860
|
+
The member-base adapter reuses everything `CasbinGuard` already prepared on the request (the verified `payload`, the `enforcer`, and your possibly-customized `casbinPermissionChecker`), so WMS enforces with the **exact same** policy engine as your own resolvers — with zero member-base import on the WMS side. Requirement: keep member-base's global guard enabled (default) and expose the request on the GraphQL context as `req` (Apollo's default).
|
|
861
|
+
|
|
862
|
+
#### Other auth systems
|
|
863
|
+
|
|
864
|
+
`authChecker` is a plain function — `(execContext, { resource, action }) => boolean | Promise<boolean>`. For JWT/RBAC without Casbin, read your session off the `ExecutionContext` and decide there. If your global guard uses a different "public/skip" metadata key than member-base's `IS_ROUTE_PUBLIC`, change the single `HOST_SKIP_METADATA_KEY` constant in `libs/graphql/src/lib/auth/host-skip.constant.ts`.
|
|
865
|
+
|
|
866
|
+
#### No global guard at all
|
|
867
|
+
|
|
868
|
+
If you omit `authChecker`, the module stays fully permissive (handy for local dev). If you protect your own resolvers with explicit `@UseGuards(...)` instead of a global `APP_GUARD`, the host-skip marker is simply inert and WMS still self-enforces through `authChecker`.
|
|
869
|
+
|
|
870
|
+
The WMS `resource`/`action` strings are **internal metadata only** — they are not part of the GraphQL schema, so refining them later is not a breaking change for GraphQL consumers (it only affects your `map`).
|
|
871
|
+
|
|
872
|
+
---
|
|
873
|
+
|
|
874
|
+
### 8.6 Mutation events & hooks
|
|
875
|
+
|
|
876
|
+
> 📖 Condensed version. The full design, the complete list of 38 operation + 18 stock domain events, payloads, and limitations live in [**`docs/events.md`**](./events.md).
|
|
877
|
+
|
|
878
|
+
Every WMS mutation can be **intervened in** and **observed** through two opt-in hook points wired on the same `WmsModuleGraphqlModule.forRootAsync({ ... })` call as everything else:
|
|
879
|
+
|
|
880
|
+
- **`beforeHooks`** run *before any DB work* — `throw` to **veto** the mutation (zero side effects).
|
|
881
|
+
- **`afterListeners`** run *after the outermost transaction commits* — you are never notified about a rolled-back change.
|
|
882
|
+
|
|
883
|
+
```ts
|
|
884
|
+
// apps/api/src/app/app.module.ts
|
|
885
|
+
import { ForbiddenException } from '@nestjs/common';
|
|
886
|
+
import { WmsModuleGraphqlModule } from '@rytass/wms-module-graphql';
|
|
887
|
+
|
|
888
|
+
WmsModuleGraphqlModule.forRootAsync({
|
|
889
|
+
// ...the same core options as §4.2 (defaultLoaderSerialId, useFactory, etc.)...
|
|
890
|
+
|
|
891
|
+
beforeHooks: {
|
|
892
|
+
// intent payload = the raw GraphQL args; throw to abort
|
|
893
|
+
'ship-inventory-order.shipped': async ({ args, executionContext }) => {
|
|
894
|
+
if (!(await canShip(args, executionContext))) {
|
|
895
|
+
throw new ForbiddenException('shipment blocked');
|
|
896
|
+
}
|
|
897
|
+
},
|
|
898
|
+
},
|
|
899
|
+
|
|
900
|
+
afterListeners: {
|
|
901
|
+
// operation event — fires once per mutation, with the result
|
|
902
|
+
'ship-inventory-order.shipped': async ({ payload }) => {
|
|
903
|
+
await erp.markShipped(payload.id, payload.result);
|
|
904
|
+
},
|
|
905
|
+
// stock domain event — fires once per affected item (post-commit)
|
|
906
|
+
'stock.allocated': async ({ payload }) => {
|
|
907
|
+
await erp.reserve(payload.orderId, payload.itemId, payload.quantityDelta);
|
|
908
|
+
},
|
|
909
|
+
},
|
|
910
|
+
});
|
|
911
|
+
```
|
|
912
|
+
|
|
913
|
+
Two tiers of events you can listen to:
|
|
914
|
+
|
|
915
|
+
- **Operation events** — one per mutation: `material.created`, `receive-inventory-order.received`, `ship-inventory-order.shipped`, … (38 total). Payload: `{ id, result }`.
|
|
916
|
+
- **Stock domain events** — driven by the `ChangeType` state machine: `stock.allocated`, `stock.shipped`, `stock.held`, … (18 total). Payload: `{ orderId, itemId, quantityDelta, changeType, inspectionResult? }`. `orderId` matches the operation event's `id`, so you can correlate the two tiers.
|
|
917
|
+
|
|
918
|
+
Both maps are fully typed — the payload is inferred from the event-name key. With neither configured, the interceptor passes through with **zero overhead** (no behaviour change).
|
|
919
|
+
|
|
920
|
+
#### Optional: `@OnEvent` via `@nestjs/event-emitter`
|
|
921
|
+
|
|
922
|
+
Prefer decoupled subscribers? Install `@nestjs/event-emitter` (an optional peer dependency) and opt into the bridge — every event is then also emitted as `wms.<event-name>`:
|
|
923
|
+
|
|
924
|
+
```ts
|
|
925
|
+
import { EventEmitterModule } from '@nestjs/event-emitter';
|
|
926
|
+
import { wmsEventEmitterBridgeProvider } from '@rytass/wms-module-graphql/adapters/event-emitter';
|
|
927
|
+
|
|
928
|
+
@Module({
|
|
929
|
+
imports: [
|
|
930
|
+
EventEmitterModule.forRoot(),
|
|
931
|
+
WmsModuleGraphqlModule.forRootAsync({
|
|
932
|
+
...coreOptions,
|
|
933
|
+
eventEmitterBridge: wmsEventEmitterBridgeProvider, // <- enables the bridge
|
|
934
|
+
}),
|
|
935
|
+
],
|
|
936
|
+
})
|
|
937
|
+
export class AppModule {}
|
|
938
|
+
|
|
939
|
+
// elsewhere
|
|
940
|
+
@Injectable()
|
|
941
|
+
class StockSync {
|
|
942
|
+
@OnEvent('wms.stock.allocated')
|
|
943
|
+
handle(payload: WmsStockEventPayload) { /* ... */ }
|
|
944
|
+
}
|
|
945
|
+
```
|
|
946
|
+
|
|
947
|
+
The bridge is the only thing that imports `@nestjs/event-emitter`, so it stays an entirely optional dependency — the core module never requires it. `afterListeners` and the bridge can be used together.
|
|
948
|
+
|
|
949
|
+
> ⚠️ Delivery is **in-process and post-commit**, not a durable outbox: if the process crashes after commit but before dispatch, those events are lost. See [`docs/events.md` §7](./events.md#7-guarantees--limitations) for the full guarantees.
|
|
833
950
|
|
|
834
951
|
---
|
|
835
952
|
|