@rytass/wms-module-graphql 0.4.0 → 0.6.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 +56 -3
- package/adapters/event-emitter.d.ts +29 -0
- package/adapters/event-emitter.js +41 -0
- package/adapters/member-base.d.ts +11 -0
- package/adapters/member-base.js +17 -1
- package/docs/README.md +3 -0
- package/docs/api-reference.md +60 -0
- package/docs/events.md +500 -0
- package/docs/integration-guide.md +111 -2
- package/index.cjs.js +154 -1
- package/index.d.ts +2 -0
- package/index.js +3 -0
- package/lib/events/index.d.ts +5 -0
- package/lib/events/operation-result-map.d.ts +77 -0
- package/lib/events/typed-maps.d.ts +73 -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 +21 -0
- package/lib/events/wms-mutation.interceptor.js +90 -0
- package/lib/graphql.module.d.ts +24 -5
- package/lib/graphql.module.js +16 -2
- package/lib/mutations/allocate-inventory-order.mutation.js +5 -0
- package/lib/mutations/hold-inventory-order.mutation.js +5 -0
- package/lib/mutations/loader.mutation.js +7 -0
- package/lib/mutations/location.mutation.js +4 -0
- package/lib/mutations/material.mutation.js +4 -0
- package/lib/mutations/merge-batch.mutation.js +3 -0
- package/lib/mutations/quality-inspection-order.mutation.js +3 -0
- package/lib/mutations/receive-inventory-order.mutation.js +5 -0
- package/lib/mutations/reclassify-inventory-order.mutation.js +3 -0
- package/lib/mutations/scrape-order.mutation.js +3 -0
- package/lib/mutations/ship-inventory-order.mutation.js +6 -0
- package/lib/mutations/split-batch.mutation.js +3 -0
- package/lib/mutations/transfer-customer-order.mutation.js +5 -0
- package/lib/mutations/transfer-order.mutation.js +6 -0
- package/lib/mutations/transfer-vendor-order.mutation.js +5 -0
- package/lib/mutations/warehouse-map.mutation.js +3 -0
- package/package.json +13 -3
package/README.md
CHANGED
|
@@ -88,6 +88,57 @@ The member-base adapter reuses everything `CasbinGuard` already put on the reque
|
|
|
88
88
|
|
|
89
89
|
➡️ **Full design & recipes: [`docs/authorization.md`](./docs/authorization.md)** (the problem, execution order, member-base / custom-JWT / no-guard recipes, the single coupling point, public API, FAQ).
|
|
90
90
|
|
|
91
|
+
## Events & Hooks
|
|
92
|
+
|
|
93
|
+
Intervene in and get notified about mutations. Every root mutation carries a typed **operation event** (`material.created`, `ship-inventory-order.shipped`, …) and fans out **stock domain events** (`stock.allocated`, `stock.shipped`, …) driven by the `ChangeType` state machine. Wire two opt-in hook points via `forRootAsync`:
|
|
94
|
+
|
|
95
|
+
- **`beforeHooks`** run before any DB work — **throw to veto** the mutation (zero side effects).
|
|
96
|
+
- **`afterListeners`** run **after the outermost transaction commits** — never notified about rolled-back changes.
|
|
97
|
+
|
|
98
|
+
```ts
|
|
99
|
+
import { memberBaseActorExtractor } from '@rytass/wms-module-graphql/adapters/member-base';
|
|
100
|
+
|
|
101
|
+
WmsModuleGraphqlModule.forRootAsync({
|
|
102
|
+
...coreOptions,
|
|
103
|
+
|
|
104
|
+
// (v2) populate payload.actor on every event — reads req.payload → { id, domain }
|
|
105
|
+
actorExtractor: memberBaseActorExtractor,
|
|
106
|
+
|
|
107
|
+
// (v2) append materialId/batchId/locationId/loaderId to stock event payloads
|
|
108
|
+
enrichStockEvents: true,
|
|
109
|
+
|
|
110
|
+
beforeHooks: {
|
|
111
|
+
'ship-inventory-order.shipped': async ({ args, actor }) => {
|
|
112
|
+
if (!allowed(args, actor)) throw new ForbiddenException('blocked');
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
afterListeners: {
|
|
116
|
+
// v2: payload.args carries items[]; payload.actor carries { id, domain }
|
|
117
|
+
'ship-inventory-order.shipped': async ({ payload }) => {
|
|
118
|
+
await erp.markShipped(payload.id, payload.result, payload.actor);
|
|
119
|
+
},
|
|
120
|
+
// v2 with enrichStockEvents: materialId/batchId/locationId/loaderId also available
|
|
121
|
+
'stock.allocated': async ({ payload }) => { await erp.reserve(payload); },
|
|
122
|
+
},
|
|
123
|
+
});
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
**v2 payload enrichment** (upcoming minor — `core@0.3.0` / `graphql@0.6.0`):
|
|
127
|
+
|
|
128
|
+
- Operation events gain `args` (the GraphQL input that triggered the mutation) and `actor`
|
|
129
|
+
(`WmsActor | null`). Nine action mutations return `boolean`; their real information is in
|
|
130
|
+
`args` and the stock events they fan out.
|
|
131
|
+
- Stock events gain opt-in `materialId / batchId / locationId / loaderId` when
|
|
132
|
+
`enrichStockEvents: true` is set. **Caveat**: split / merge / reclassify order-items have
|
|
133
|
+
no `materialId` / `batchId` at the item level — only `locationId` / `loaderId` are
|
|
134
|
+
available for those three domains even with eager enrichment.
|
|
135
|
+
- `WmsOperationResultMap` provides a per-event typed overlay so subscribers receive the
|
|
136
|
+
concrete DTO (`MaterialDto`, `ShipInventoryOrderDto`, …) instead of `unknown`.
|
|
137
|
+
|
|
138
|
+
Post-commit delivery is guaranteed by an `AsyncLocalStorage` dispatch scope (events buffer during the request and flush only after commit). Prefer `@OnEvent`? Opt into the optional [`@nestjs/event-emitter`](https://docs.nestjs.com/techniques/events) bridge from `@rytass/wms-module-graphql/adapters/event-emitter` — every event also emits as `wms.<event>`. Nothing configured ⇒ no events, zero overhead.
|
|
139
|
+
|
|
140
|
+
➡️ **Full design, the complete 56-event list & limitations: [`docs/events.md`](./docs/events.md)**.
|
|
141
|
+
|
|
91
142
|
## DataLoader context
|
|
92
143
|
|
|
93
144
|
The module ships a `HelperModule` (auto-registered) that provides a singleton `LoaderService`. To avoid N+1 queries, the WMS resolvers expect a per-request DataLoader trio at `context.loaders`:
|
|
@@ -124,14 +175,16 @@ Full documentation is shipped inside this package under the `docs/` directory (a
|
|
|
124
175
|
|---|---|
|
|
125
176
|
| [`docs/integration-guide.md`](./docs/integration-guide.md) | Step-by-step wiring guide for NestJS + Next.js consumers |
|
|
126
177
|
| [`docs/authorization.md`](./docs/authorization.md) | Complete resolver-authorization design & integration recipes (member-base / custom JWT / no-guard) |
|
|
178
|
+
| [`docs/events.md`](./docs/events.md) | Mutation event/hook system — before-hooks (veto), post-commit after-listeners, complete event list, event-emitter bridge |
|
|
127
179
|
| [`docs/api-reference.md`](./docs/api-reference.md) | Complete export catalog with signatures and usage examples |
|
|
128
180
|
|
|
129
181
|
GitHub hosted links (always up-to-date):
|
|
130
182
|
|
|
131
|
-
- [Integration Guide — §4 Backend setup](https://github.com/Rytass/WMS-Module/blob/main/docs/integration-guide.md#4-backend-setup-nestjs--graphql--typeorm)
|
|
132
|
-
- [Integration Guide — §13.1 Existing GraphQLModule co-existence](https://github.com/Rytass/WMS-Module/blob/main/docs/integration-guide.md#13-integrating-into-an-existing-nestjs--nextjs-project)
|
|
183
|
+
- [Integration Guide — §4 Backend setup](https://github.com/Rytass/WMS-Module/blob/main/libs/graphql/docs/integration-guide.md#4-backend-setup-nestjs--graphql--typeorm)
|
|
184
|
+
- [Integration Guide — §13.1 Existing GraphQLModule co-existence](https://github.com/Rytass/WMS-Module/blob/main/libs/graphql/docs/integration-guide.md#13-integrating-into-an-existing-nestjs--nextjs-project)
|
|
133
185
|
- [Authorization — design & recipes](https://github.com/Rytass/WMS-Module/blob/main/libs/graphql/docs/authorization.md)
|
|
134
|
-
- [
|
|
186
|
+
- [Events & Hooks — design & event list](https://github.com/Rytass/WMS-Module/blob/main/libs/graphql/docs/events.md)
|
|
187
|
+
- [API Reference — @rytass/wms-module-graphql](https://github.com/Rytass/WMS-Module/blob/main/libs/graphql/docs/api-reference.md#rytasswms-module-graphql)
|
|
135
188
|
|
|
136
189
|
## License
|
|
137
190
|
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { Provider } from '@nestjs/common';
|
|
2
|
+
/**
|
|
3
|
+
* Bridges WMS domain events onto `@nestjs/event-emitter`'s `EventEmitter2`, so
|
|
4
|
+
* consumers can subscribe with `@OnEvent('wms.<event-name>')` instead of (or in
|
|
5
|
+
* addition to) the typed `afterListeners`.
|
|
6
|
+
*
|
|
7
|
+
* Pass this into `WmsModuleGraphqlModule.forRootAsync({ eventEmitterBridge })`.
|
|
8
|
+
* Importing this module is the only place `@nestjs/event-emitter` is referenced,
|
|
9
|
+
* keeping it an entirely optional peer dependency — the core graphql module
|
|
10
|
+
* never imports it.
|
|
11
|
+
*
|
|
12
|
+
* Prerequisite: register `EventEmitterModule.forRoot()` in your app so an
|
|
13
|
+
* `EventEmitter2` instance is available for injection.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```ts
|
|
17
|
+
* import { wmsEventEmitterBridgeProvider } from '@rytass/wms-module-graphql/adapters/event-emitter';
|
|
18
|
+
*
|
|
19
|
+
* WmsModuleGraphqlModule.forRootAsync({
|
|
20
|
+
* ...coreOptions,
|
|
21
|
+
* eventEmitterBridge: wmsEventEmitterBridgeProvider,
|
|
22
|
+
* });
|
|
23
|
+
*
|
|
24
|
+
* // elsewhere
|
|
25
|
+
* @OnEvent('wms.stock.allocated')
|
|
26
|
+
* handle(payload: WmsStockEventPayload) { ... }
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
export declare const wmsEventEmitterBridgeProvider: Provider;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { EventEmitter2 } from '@nestjs/event-emitter';
|
|
2
|
+
import { WMS_EVENT_EMITTER_BRIDGE } from '@rytass/wms-module-core';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Bridges WMS domain events onto `@nestjs/event-emitter`'s `EventEmitter2`, so
|
|
6
|
+
* consumers can subscribe with `@OnEvent('wms.<event-name>')` instead of (or in
|
|
7
|
+
* addition to) the typed `afterListeners`.
|
|
8
|
+
*
|
|
9
|
+
* Pass this into `WmsModuleGraphqlModule.forRootAsync({ eventEmitterBridge })`.
|
|
10
|
+
* Importing this module is the only place `@nestjs/event-emitter` is referenced,
|
|
11
|
+
* keeping it an entirely optional peer dependency — the core graphql module
|
|
12
|
+
* never imports it.
|
|
13
|
+
*
|
|
14
|
+
* Prerequisite: register `EventEmitterModule.forRoot()` in your app so an
|
|
15
|
+
* `EventEmitter2` instance is available for injection.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```ts
|
|
19
|
+
* import { wmsEventEmitterBridgeProvider } from '@rytass/wms-module-graphql/adapters/event-emitter';
|
|
20
|
+
*
|
|
21
|
+
* WmsModuleGraphqlModule.forRootAsync({
|
|
22
|
+
* ...coreOptions,
|
|
23
|
+
* eventEmitterBridge: wmsEventEmitterBridgeProvider,
|
|
24
|
+
* });
|
|
25
|
+
*
|
|
26
|
+
* // elsewhere
|
|
27
|
+
* @OnEvent('wms.stock.allocated')
|
|
28
|
+
* handle(payload: WmsStockEventPayload) { ... }
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
const wmsEventEmitterBridgeProvider = {
|
|
32
|
+
provide: WMS_EVENT_EMITTER_BRIDGE,
|
|
33
|
+
useFactory: (emitter) => ({
|
|
34
|
+
forward: (event) => {
|
|
35
|
+
emitter.emit(`wms.${event.name}`, event.payload);
|
|
36
|
+
},
|
|
37
|
+
}),
|
|
38
|
+
inject: [EventEmitter2],
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export { wmsEventEmitterBridgeProvider };
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { WmsActorExtractor } from '@rytass/wms-module-core';
|
|
1
2
|
import type { WmsAuthChecker, WmsOperationRequirement } from '../lib/auth';
|
|
2
3
|
/**
|
|
3
4
|
* A Casbin `[subject, action]` tuple, e.g. `['wms:material', 'read']`.
|
|
@@ -33,3 +34,13 @@ export interface MemberBaseWmsAuthCheckerOptions {
|
|
|
33
34
|
* (Apollo's default).
|
|
34
35
|
*/
|
|
35
36
|
export declare const createMemberBaseWmsAuthChecker: (options: MemberBaseWmsAuthCheckerOptions) => WmsAuthChecker;
|
|
37
|
+
/**
|
|
38
|
+
* A {@link WmsActorExtractor} for `@rytass/member-base-nestjs-module`. Reads the
|
|
39
|
+
* verified JWT payload that `CasbinGuard` attaches to the request and surfaces it
|
|
40
|
+
* as the event/hook `actor`. Returns `null` for unauthenticated requests.
|
|
41
|
+
*
|
|
42
|
+
* Pass to `WmsModuleGraphqlModule.forRootAsync({ actorExtractor })` so every
|
|
43
|
+
* operation event and before-hook carries `{ id, domain }`. Imports nothing from
|
|
44
|
+
* member-base — it only reads the runtime request fields.
|
|
45
|
+
*/
|
|
46
|
+
export declare const memberBaseActorExtractor: WmsActorExtractor;
|
package/adapters/member-base.js
CHANGED
|
@@ -43,5 +43,21 @@ const createMemberBaseWmsAuthChecker = (options) => {
|
|
|
43
43
|
});
|
|
44
44
|
};
|
|
45
45
|
};
|
|
46
|
+
/**
|
|
47
|
+
* A {@link WmsActorExtractor} for `@rytass/member-base-nestjs-module`. Reads the
|
|
48
|
+
* verified JWT payload that `CasbinGuard` attaches to the request and surfaces it
|
|
49
|
+
* as the event/hook `actor`. Returns `null` for unauthenticated requests.
|
|
50
|
+
*
|
|
51
|
+
* Pass to `WmsModuleGraphqlModule.forRootAsync({ actorExtractor })` so every
|
|
52
|
+
* operation event and before-hook carries `{ id, domain }`. Imports nothing from
|
|
53
|
+
* member-base — it only reads the runtime request fields.
|
|
54
|
+
*/
|
|
55
|
+
const memberBaseActorExtractor = (context) => {
|
|
56
|
+
const payload = extractRequest(context)?.payload;
|
|
57
|
+
if (!payload?.id) {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
return { id: payload.id, domain: payload.domain, raw: payload };
|
|
61
|
+
};
|
|
46
62
|
|
|
47
|
-
export { createMemberBaseWmsAuthChecker };
|
|
63
|
+
export { createMemberBaseWmsAuthChecker, memberBaseActorExtractor };
|
package/docs/README.md
CHANGED
|
@@ -8,6 +8,7 @@ Reference material for consumers of `@rytass/wms-module-{core,graphql,react}`.
|
|
|
8
8
|
|------------------------------------------------|---------------------------------------------------------------------------------------------------|
|
|
9
9
|
| [Integration Guide](./integration-guide.md) | Step-by-step instructions for wiring the three packages into a new consumer project (NestJS + Next.js + PostgreSQL). Includes ready-to-paste `_app.tsx`, `app.module.ts`, `next.config.ts`, the 19 Pages Router shell pattern, AuthGuard stub, seed script template, deployment notes, and known issues. |
|
|
10
10
|
| [Authorization](./authorization.md) | Complete design of the resolver-level authorization protocol (`@WmsOperation` + `WmsAuthGuard` + `authChecker`). Explains the global deny-by-default guard pitfall, execution order, and integration recipes for member-base / Casbin, custom JWT / RBAC, and no-guard setups. Read this if any WMS query/mutation returns *forbidden*. |
|
|
11
|
+
| [Events & Hooks](./events.md) | The mutation event/hook system — `beforeHooks` (veto before any DB work) and `afterListeners` (post-commit notify), the `AsyncLocalStorage` post-commit mechanism, the optional `@nestjs/event-emitter` bridge, and the complete list of 38 operation + 18 stock domain events with payloads. |
|
|
11
12
|
| [API Reference](./api-reference.md) | Complete export catalog for all three packages — every NestJS module, TypeORM entity, GraphQL resolver, DTO, React component, hook, and utility, organized by role with source paths and usage examples. Mirrors the actual `src/index.ts` barrel exports of `@rytass/wms-module-graphql@0.3.0` and `@rytass/wms-module-{core,react}@0.1.6`. |
|
|
12
13
|
|
|
13
14
|
## Quick links
|
|
@@ -26,6 +27,8 @@ Reference material for consumers of `@rytass/wms-module-{core,graphql,react}`.
|
|
|
26
27
|
| Replace the `AuthGuard` stub with real auth | [Integration Guide section 8.4](./integration-guide.md#84-real-auth-integration) |
|
|
27
28
|
| Authorize WMS resolvers with member-base / Casbin / RBAC | [Authorization](./authorization.md) |
|
|
28
29
|
| Fix "every WMS query/mutation is forbidden" | [Authorization section 1](./authorization.md#1-the-problem) |
|
|
30
|
+
| Hook into / get notified about mutations | [Events & Hooks](./events.md) |
|
|
31
|
+
| See every event the system emits | [Events & Hooks section 5](./events.md#5-complete-event-list) |
|
|
29
32
|
| Decide between PostgreSQL and SQLite | [Integration Guide section 9](./integration-guide.md#9-database-support-matrix) |
|
|
30
33
|
| Build a custom UI on top of the lib's GraphQL operations| [Integration Guide section 7.3](./integration-guide.md#73-graphql-operations--typed-hooks) |
|
|
31
34
|
|
package/docs/api-reference.md
CHANGED
|
@@ -449,6 +449,66 @@ The adapter imports nothing from member-base — it reads the `payload` / `enfor
|
|
|
449
449
|
|
|
450
450
|
---
|
|
451
451
|
|
|
452
|
+
## Events & Hooks
|
|
453
|
+
|
|
454
|
+
Opt-in `beforeHooks` (veto) and post-commit `afterListeners` for every mutation. Each root mutation carries `@WmsMutationEvent({ event })` (already applied); a per-mutation interceptor runs the before-hook, opens an `AsyncLocalStorage` dispatch scope, and flushes collected events after the transaction commits. Stock domain events are collected centrally by a TypeORM subscriber on `*_order_change` inserts. Full design & the complete event list: [`docs/events.md`](./events.md).
|
|
455
|
+
|
|
456
|
+
### Root exports (`@rytass/wms-module-graphql`, re-exported from `@rytass/wms-module-core`)
|
|
457
|
+
|
|
458
|
+
| Export | Kind | Signature / shape |
|
|
459
|
+
|-----------------------------------------------------------------|-----------|----------------------------------------------------------------------------------------------------------------------------------|
|
|
460
|
+
| `WmsMutationEvent` | decorator | `({ event: WmsOperationEventName, resolveId? }) => MethodDecorator` |
|
|
461
|
+
| `WmsEventModuleOptions` | type | `{ beforeHooks?, afterListeners?, actorExtractor?, enrichStockEvents? }` — part of `forRootAsync` options |
|
|
462
|
+
| `WmsBeforeHookMap` | type | `{ [N in WmsEventName]?: (ctx: WmsBeforeHookContext<N>) => void \| Promise<void> }` |
|
|
463
|
+
| `WmsAfterListenerMap` | type | `{ [N in WmsEventName]?: (ctx: WmsAfterEventContext<N>) => void \| Promise<void> }` |
|
|
464
|
+
| `WmsBeforeHookContext` | type | `{ event; args: Record<string, unknown>; executionContext: ExecutionContext; actor: WmsActor \| null }` (v2 adds `actor`) |
|
|
465
|
+
| `WmsAfterEventContext` | type | `{ event; payload: WmsEventPayloadMap[event] }` |
|
|
466
|
+
| `WmsDomainEvent` | type | `{ name: WmsEventName; payload }` |
|
|
467
|
+
| `WmsEventName` / `WmsOperationEventName` / `WmsStockEventName` | type | event-name unions (38 operation + 18 stock) |
|
|
468
|
+
| `WmsEventPayloadMap` | type | event name → payload |
|
|
469
|
+
| `WmsOperationEventPayload` | type | v1: `{ id: string \| null; result: unknown }` — v2: `{ id; result; args: Readonly<Record<string, unknown>>; actor: WmsActor \| null }` |
|
|
470
|
+
| `WmsStockEventPayload` | type | `{ orderId; itemId; quantityDelta; changeType: ChangeType \| null; inspectionResult? }` — v2 adds optional `materialId?`, `batchId?`, `locationId?`, `loaderId?` when `enrichStockEvents: true` |
|
|
471
|
+
| `WmsActor` | type | `{ readonly id: string; readonly domain?: string; readonly raw?: unknown }` — actor identity (v2) |
|
|
472
|
+
| `WmsActorExtractor` | type | `(context: ExecutionContext) => WmsActor \| null` — injectable extractor (v2); token `WMS_ACTOR_EXTRACTOR` |
|
|
473
|
+
| `WmsOperationResultMap` | type | Maps each `WmsOperationEventName` to its concrete `result` DTO (e.g. `'material.created' → MaterialDto`); powers typed listeners (v2) |
|
|
474
|
+
|
|
475
|
+
### Adapter subpath (`@rytass/wms-module-graphql/adapters/event-emitter`)
|
|
476
|
+
|
|
477
|
+
| Export | Kind | Purpose |
|
|
478
|
+
|-------------------------------|------------|------------------------------------------------------------------------------------------------------------------------------------------|
|
|
479
|
+
| `wmsEventEmitterBridgeProvider` | `Provider` | Pass to `forRootAsync({ eventEmitterBridge })`; fans every event onto `EventEmitter2` as `wms.<event>` for `@OnEvent`. Requires `@nestjs/event-emitter` (optional peer) + `EventEmitterModule.forRoot()`. |
|
|
480
|
+
|
|
481
|
+
### Adapter subpath (`@rytass/wms-module-graphql/adapters/member-base`) — event extras (v2)
|
|
482
|
+
|
|
483
|
+
| Export | Kind | Purpose |
|
|
484
|
+
|-----------------------------|-----------|-------------------------------------------------------------------------------------------------------------------------------------|
|
|
485
|
+
| `memberBaseActorExtractor` | extractor | `WmsActorExtractor` that reads `GqlExecutionContext.create(ctx).getContext().req?.payload` → `{ id, domain }`. Pass to `forRootAsync({ actorExtractor })`. |
|
|
486
|
+
|
|
487
|
+
```ts
|
|
488
|
+
import { memberBaseActorExtractor } from '@rytass/wms-module-graphql/adapters/member-base';
|
|
489
|
+
import { wmsEventEmitterBridgeProvider } from '@rytass/wms-module-graphql/adapters/event-emitter';
|
|
490
|
+
|
|
491
|
+
WmsModuleGraphqlModule.forRootAsync({
|
|
492
|
+
...coreOptions,
|
|
493
|
+
actorExtractor: memberBaseActorExtractor, // v2: populates payload.actor
|
|
494
|
+
enrichStockEvents: true, // v2: appends ids to stock payloads
|
|
495
|
+
beforeHooks: {
|
|
496
|
+
'ship-inventory-order.shipped': async ({ args, actor }) => {
|
|
497
|
+
if (!ok(args, actor)) throw new ForbiddenException();
|
|
498
|
+
},
|
|
499
|
+
},
|
|
500
|
+
afterListeners: {
|
|
501
|
+
'stock.allocated': async ({ payload }) => { await sync(payload); },
|
|
502
|
+
},
|
|
503
|
+
// optional @OnEvent bridge:
|
|
504
|
+
// eventEmitterBridge: wmsEventEmitterBridgeProvider,
|
|
505
|
+
});
|
|
506
|
+
```
|
|
507
|
+
|
|
508
|
+
`beforeHooks` / `afterListeners` are **not** part of the GraphQL schema — they are runtime hooks. With none configured the interceptor passes through with zero overhead. All v2 options (`actorExtractor`, `enrichStockEvents`) are fully optional; omitting them preserves the v1 behaviour exactly.
|
|
509
|
+
|
|
510
|
+
---
|
|
511
|
+
|
|
452
512
|
## Resolvers(`@Resolver()` decorator class)
|
|
453
513
|
|
|
454
514
|
以下 Resolver 類別處理 GraphQL `@ResolveField`,解析關聯欄位(防止 N+1):
|