@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
package/index.d.ts
CHANGED
|
@@ -3,3 +3,7 @@ export { LoaderService } from './lib/helpers/loader.service';
|
|
|
3
3
|
export type { LoaderOptions, LoaderOptionsWithPagination, } from './lib/helpers/loader.service';
|
|
4
4
|
export type { LoaderType } from './lib/helpers/dataloader.type';
|
|
5
5
|
export { createWmsLoaders, type WmsLoaders, } from './lib/helpers/create-wms-loaders';
|
|
6
|
+
export { HOST_SKIP_METADATA_KEY, WmsAuthGuard, WmsOperation, WMS_AUTH_OPTIONS, WMS_OPERATION_METADATA, type WmsAuthChecker, type WmsAuthModuleOptions, type WmsOperationRequirement, } from './lib/auth';
|
|
7
|
+
export type { WmsModuleGraphqlModuleAsyncOptions } from './lib/graphql.module';
|
|
8
|
+
export { WmsMutationEvent, WmsMutationInterceptor, WMS_MUTATION_EVENT_METADATA, type WmsMutationEventConfig, } from './lib/events';
|
|
9
|
+
export type { WmsAfterEventContext, WmsAfterListener, WmsAfterListenerMap, WmsBeforeHook, WmsBeforeHookContext, WmsBeforeHookMap, WmsDomainEvent, WmsEventModuleOptions, WmsEventName, WmsEventPayloadMap, WmsOperationEventName, WmsOperationEventPayload, WmsStockEventName, WmsStockEventPayload, } from '@rytass/wms-module-core';
|
package/index.js
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
1
|
export { WmsModuleGraphqlModule } from './lib/graphql.module.js';
|
|
2
2
|
export { LoaderService } from './lib/helpers/loader.service.js';
|
|
3
3
|
export { createWmsLoaders } from './lib/helpers/create-wms-loaders.js';
|
|
4
|
+
export { HOST_SKIP_METADATA_KEY } from './lib/auth/host-skip.constant.js';
|
|
5
|
+
export { WmsAuthGuard } from './lib/auth/wms-auth.guard.js';
|
|
6
|
+
export { WmsOperation } from './lib/auth/wms-operation.decorator.js';
|
|
7
|
+
export { WMS_AUTH_OPTIONS, WMS_OPERATION_METADATA } from './lib/auth/wms-auth.types.js';
|
|
8
|
+
export { WmsMutationEvent } from './lib/events/wms-mutation-event.decorator.js';
|
|
9
|
+
export { WMS_MUTATION_EVENT_METADATA } from './lib/events/wms-mutation-event.constants.js';
|
|
10
|
+
export { WmsMutationInterceptor } from './lib/events/wms-mutation.interceptor.js';
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Metadata key stamped on every `@WmsOperation` handler so that a host
|
|
3
|
+
* application's *globally registered* auth guard (`APP_GUARD`) treats WMS
|
|
4
|
+
* resolvers as "skip / public" and defers the real authorization decision to
|
|
5
|
+
* {@link WmsAuthGuard}.
|
|
6
|
+
*
|
|
7
|
+
* Why this exists
|
|
8
|
+
* ---------------
|
|
9
|
+
* A deny-by-default global guard — e.g. the `CasbinGuard` from
|
|
10
|
+
* `@rytass/member-base-nestjs-module` — blankets every resolver in the compiled
|
|
11
|
+
* schema, including the ones this library owns. WMS resolvers carry no
|
|
12
|
+
* host-specific permission metadata, so such a guard rejects them all. By
|
|
13
|
+
* stamping this key WMS tells the host guard "let me through", and then runs
|
|
14
|
+
* its own {@link WmsAuthGuard} (wired to the consumer's `authChecker`) to do the
|
|
15
|
+
* actual enforcement.
|
|
16
|
+
*
|
|
17
|
+
* The single coupling point
|
|
18
|
+
* -------------------------
|
|
19
|
+
* The default value matches `@rytass/member-base-nestjs-module`'s
|
|
20
|
+
* `IS_ROUTE_PUBLIC` metadata key. This is the *one and only* place WMS assumes
|
|
21
|
+
* anything about a specific host guard. If your host guard uses a different
|
|
22
|
+
* "public / skip" metadata key, fork this constant — nothing else changes.
|
|
23
|
+
*
|
|
24
|
+
* If you do not register a global guard at all (or set
|
|
25
|
+
* `enableGlobalGuard: false`), this marker is simply inert.
|
|
26
|
+
*/
|
|
27
|
+
export declare const HOST_SKIP_METADATA_KEY = "IS_ROUTE_PUBLIC";
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Metadata key stamped on every `@WmsOperation` handler so that a host
|
|
3
|
+
* application's *globally registered* auth guard (`APP_GUARD`) treats WMS
|
|
4
|
+
* resolvers as "skip / public" and defers the real authorization decision to
|
|
5
|
+
* {@link WmsAuthGuard}.
|
|
6
|
+
*
|
|
7
|
+
* Why this exists
|
|
8
|
+
* ---------------
|
|
9
|
+
* A deny-by-default global guard — e.g. the `CasbinGuard` from
|
|
10
|
+
* `@rytass/member-base-nestjs-module` — blankets every resolver in the compiled
|
|
11
|
+
* schema, including the ones this library owns. WMS resolvers carry no
|
|
12
|
+
* host-specific permission metadata, so such a guard rejects them all. By
|
|
13
|
+
* stamping this key WMS tells the host guard "let me through", and then runs
|
|
14
|
+
* its own {@link WmsAuthGuard} (wired to the consumer's `authChecker`) to do the
|
|
15
|
+
* actual enforcement.
|
|
16
|
+
*
|
|
17
|
+
* The single coupling point
|
|
18
|
+
* -------------------------
|
|
19
|
+
* The default value matches `@rytass/member-base-nestjs-module`'s
|
|
20
|
+
* `IS_ROUTE_PUBLIC` metadata key. This is the *one and only* place WMS assumes
|
|
21
|
+
* anything about a specific host guard. If your host guard uses a different
|
|
22
|
+
* "public / skip" metadata key, fork this constant — nothing else changes.
|
|
23
|
+
*
|
|
24
|
+
* If you do not register a global guard at all (or set
|
|
25
|
+
* `enableGlobalGuard: false`), this marker is simply inert.
|
|
26
|
+
*/
|
|
27
|
+
const HOST_SKIP_METADATA_KEY = 'IS_ROUTE_PUBLIC';
|
|
28
|
+
|
|
29
|
+
export { HOST_SKIP_METADATA_KEY };
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { HOST_SKIP_METADATA_KEY } from './host-skip.constant';
|
|
2
|
+
export { WmsAuthGuard } from './wms-auth.guard';
|
|
3
|
+
export { WmsOperation } from './wms-operation.decorator';
|
|
4
|
+
export { WMS_AUTH_OPTIONS, WMS_OPERATION_METADATA, type WmsAuthChecker, type WmsAuthModuleOptions, type WmsOperationRequirement, } from './wms-auth.types';
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type CanActivate, type ExecutionContext } from '@nestjs/common';
|
|
2
|
+
import { Reflector } from '@nestjs/core';
|
|
3
|
+
import { type WmsAuthModuleOptions } from './wms-auth.types';
|
|
4
|
+
/**
|
|
5
|
+
* Route-level guard applied automatically by `@WmsOperation`. It reads the
|
|
6
|
+
* requirement declared on the handler and delegates the allow/deny decision to
|
|
7
|
+
* the consumer-supplied {@link WmsAuthModuleOptions.authChecker}.
|
|
8
|
+
*
|
|
9
|
+
* It is deliberately permissive in two cases:
|
|
10
|
+
* - the handler carries no requirement (field resolvers reached only through an
|
|
11
|
+
* already-authorized root operation), and
|
|
12
|
+
* - no `authChecker` was configured (local dev / auth handled elsewhere).
|
|
13
|
+
*/
|
|
14
|
+
export declare class WmsAuthGuard implements CanActivate {
|
|
15
|
+
private readonly reflector;
|
|
16
|
+
private readonly options;
|
|
17
|
+
constructor(reflector: Reflector, options: WmsAuthModuleOptions);
|
|
18
|
+
canActivate(context: ExecutionContext): boolean | Promise<boolean>;
|
|
19
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { __decorate, __param, __metadata } from 'tslib';
|
|
2
|
+
import { Injectable, Inject } from '@nestjs/common';
|
|
3
|
+
import { Reflector } from '@nestjs/core';
|
|
4
|
+
import { WMS_AUTH_OPTIONS, WMS_OPERATION_METADATA } from './wms-auth.types.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Route-level guard applied automatically by `@WmsOperation`. It reads the
|
|
8
|
+
* requirement declared on the handler and delegates the allow/deny decision to
|
|
9
|
+
* the consumer-supplied {@link WmsAuthModuleOptions.authChecker}.
|
|
10
|
+
*
|
|
11
|
+
* It is deliberately permissive in two cases:
|
|
12
|
+
* - the handler carries no requirement (field resolvers reached only through an
|
|
13
|
+
* already-authorized root operation), and
|
|
14
|
+
* - no `authChecker` was configured (local dev / auth handled elsewhere).
|
|
15
|
+
*/
|
|
16
|
+
let WmsAuthGuard = class WmsAuthGuard {
|
|
17
|
+
reflector;
|
|
18
|
+
options;
|
|
19
|
+
constructor(reflector, options) {
|
|
20
|
+
this.reflector = reflector;
|
|
21
|
+
this.options = options;
|
|
22
|
+
}
|
|
23
|
+
canActivate(context) {
|
|
24
|
+
const requirement = this.reflector.get(WMS_OPERATION_METADATA, context.getHandler());
|
|
25
|
+
if (!requirement) {
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
28
|
+
const { authChecker } = this.options;
|
|
29
|
+
if (!authChecker) {
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
return authChecker(context, requirement);
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
WmsAuthGuard = __decorate([
|
|
36
|
+
Injectable(),
|
|
37
|
+
__param(1, Inject(WMS_AUTH_OPTIONS)),
|
|
38
|
+
__metadata("design:paramtypes", [Reflector, Object])
|
|
39
|
+
], WmsAuthGuard);
|
|
40
|
+
|
|
41
|
+
export { WmsAuthGuard };
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { ExecutionContext } from '@nestjs/common';
|
|
2
|
+
/**
|
|
3
|
+
* A single, host-neutral permission requirement attached to a WMS root
|
|
4
|
+
* operation. `resource` and `action` are plain strings on purpose — the WMS
|
|
5
|
+
* library never knows (nor cares) how a consumer maps them onto Casbin, RBAC
|
|
6
|
+
* tiers, JWT scopes or anything else. The mapping lives entirely in the
|
|
7
|
+
* consumer-supplied {@link WmsAuthChecker}.
|
|
8
|
+
*/
|
|
9
|
+
export interface WmsOperationRequirement {
|
|
10
|
+
readonly resource: string;
|
|
11
|
+
readonly action: string;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* The authorization contract a consumer wires into
|
|
15
|
+
* `WmsModuleGraphqlModule.forRootAsync({ authChecker })`. It receives the live
|
|
16
|
+
* NestJS {@link ExecutionContext} (so the consumer can reach the request,
|
|
17
|
+
* GraphQL context, current member, enforcer, etc.) plus the requirement
|
|
18
|
+
* declared by `@WmsOperation` on the resolver being invoked.
|
|
19
|
+
*
|
|
20
|
+
* Return `true` to allow, `false` to deny. Throwing is also fine — the
|
|
21
|
+
* exception propagates exactly like any guard rejection.
|
|
22
|
+
*/
|
|
23
|
+
export type WmsAuthChecker = (context: ExecutionContext, requirement: WmsOperationRequirement) => boolean | Promise<boolean>;
|
|
24
|
+
/**
|
|
25
|
+
* Options accepted by `WmsModuleGraphqlModule.forRootAsync` that govern
|
|
26
|
+
* authorization. Intentionally optional: when `authChecker` is omitted the
|
|
27
|
+
* module stays fully permissive, preserving the pre-auth release behaviour for
|
|
28
|
+
* local development and consumers that handle authorization elsewhere.
|
|
29
|
+
*/
|
|
30
|
+
export interface WmsAuthModuleOptions {
|
|
31
|
+
readonly authChecker?: WmsAuthChecker;
|
|
32
|
+
}
|
|
33
|
+
/** DI token holding the resolved {@link WmsAuthModuleOptions}. */
|
|
34
|
+
export declare const WMS_AUTH_OPTIONS: unique symbol;
|
|
35
|
+
/** Reflector metadata key carrying the {@link WmsOperationRequirement}. */
|
|
36
|
+
export declare const WMS_OPERATION_METADATA = "wms:operation";
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/** DI token holding the resolved {@link WmsAuthModuleOptions}. */
|
|
2
|
+
const WMS_AUTH_OPTIONS = Symbol('WMS_AUTH_OPTIONS');
|
|
3
|
+
/** Reflector metadata key carrying the {@link WmsOperationRequirement}. */
|
|
4
|
+
const WMS_OPERATION_METADATA = 'wms:operation';
|
|
5
|
+
|
|
6
|
+
export { WMS_AUTH_OPTIONS, WMS_OPERATION_METADATA };
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { type WmsOperationRequirement } from './wms-auth.types';
|
|
2
|
+
/**
|
|
3
|
+
* Declares the permission requirement for a WMS root operation and wires up its
|
|
4
|
+
* enforcement. Apply it to every `@Query` / `@Mutation` the library exposes.
|
|
5
|
+
*
|
|
6
|
+
* It bundles three concerns so a single annotation keeps a resolver
|
|
7
|
+
* self-protecting and host-friendly:
|
|
8
|
+
*
|
|
9
|
+
* 1. {@link WMS_OPERATION_METADATA} — the host-neutral `{ resource, action }`
|
|
10
|
+
* that {@link WmsAuthGuard} hands to the consumer's `authChecker`.
|
|
11
|
+
* 2. {@link HOST_SKIP_METADATA_KEY} — tells a host's global guard to defer (see
|
|
12
|
+
* that constant for the single coupling point with member-base).
|
|
13
|
+
* 3. `@UseGuards(WmsAuthGuard)` — the route-level guard that performs the real
|
|
14
|
+
* authorization through the configured `authChecker`.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```ts
|
|
18
|
+
* @Query(() => MaterialDto)
|
|
19
|
+
* @WmsOperation({ resource: 'material', action: 'read' })
|
|
20
|
+
* material(@Args('id') id: string): Promise<MaterialDto> { ... }
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
export declare const WmsOperation: (requirement: WmsOperationRequirement) => MethodDecorator;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { applyDecorators, SetMetadata, UseGuards } from '@nestjs/common';
|
|
2
|
+
import { HOST_SKIP_METADATA_KEY } from './host-skip.constant.js';
|
|
3
|
+
import { WmsAuthGuard } from './wms-auth.guard.js';
|
|
4
|
+
import { WMS_OPERATION_METADATA } from './wms-auth.types.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Declares the permission requirement for a WMS root operation and wires up its
|
|
8
|
+
* enforcement. Apply it to every `@Query` / `@Mutation` the library exposes.
|
|
9
|
+
*
|
|
10
|
+
* It bundles three concerns so a single annotation keeps a resolver
|
|
11
|
+
* self-protecting and host-friendly:
|
|
12
|
+
*
|
|
13
|
+
* 1. {@link WMS_OPERATION_METADATA} — the host-neutral `{ resource, action }`
|
|
14
|
+
* that {@link WmsAuthGuard} hands to the consumer's `authChecker`.
|
|
15
|
+
* 2. {@link HOST_SKIP_METADATA_KEY} — tells a host's global guard to defer (see
|
|
16
|
+
* that constant for the single coupling point with member-base).
|
|
17
|
+
* 3. `@UseGuards(WmsAuthGuard)` — the route-level guard that performs the real
|
|
18
|
+
* authorization through the configured `authChecker`.
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* ```ts
|
|
22
|
+
* @Query(() => MaterialDto)
|
|
23
|
+
* @WmsOperation({ resource: 'material', action: 'read' })
|
|
24
|
+
* material(@Args('id') id: string): Promise<MaterialDto> { ... }
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
const WmsOperation = (requirement) => applyDecorators(SetMetadata(WMS_OPERATION_METADATA, requirement), SetMetadata(HOST_SKIP_METADATA_KEY, true), UseGuards(WmsAuthGuard));
|
|
28
|
+
|
|
29
|
+
export { WmsOperation };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { WmsOperationEventName } from '@rytass/wms-module-core';
|
|
2
|
+
/** Reflector metadata key carrying the {@link WmsMutationEventConfig}. */
|
|
3
|
+
export declare const WMS_MUTATION_EVENT_METADATA = "wms:mutation-event";
|
|
4
|
+
export interface WmsMutationEventConfig {
|
|
5
|
+
/** The operation event emitted for this mutation (post-commit). */
|
|
6
|
+
readonly event: WmsOperationEventName;
|
|
7
|
+
/**
|
|
8
|
+
* Optional override for deriving the operation's primary id from the handler
|
|
9
|
+
* result and/or arguments. Defaults to `result.id ?? args.id ?? null`.
|
|
10
|
+
*/
|
|
11
|
+
readonly resolveId?: (result: unknown, args: Readonly<Record<string, unknown>>) => string | null;
|
|
12
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type WmsMutationEventConfig } from './wms-mutation-event.constants';
|
|
2
|
+
/**
|
|
3
|
+
* Declares the operation event for a WMS root mutation and wires the
|
|
4
|
+
* {@link WmsMutationInterceptor} that runs before-hooks (veto) and flushes
|
|
5
|
+
* collected events post-commit. Apply alongside `@WmsOperation` on each mutation.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```ts
|
|
9
|
+
* @Mutation(() => ShipInventoryOrderDto)
|
|
10
|
+
* @WmsOperation({ resource: 'ship-inventory-order', action: 'ship' })
|
|
11
|
+
* @WmsMutationEvent({ event: 'ship-inventory-order.shipped' })
|
|
12
|
+
* ship(@Args() args: ShipArgs): Promise<boolean> { ... }
|
|
13
|
+
* ```
|
|
14
|
+
*/
|
|
15
|
+
export declare const WmsMutationEvent: (config: WmsMutationEventConfig) => MethodDecorator;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { applyDecorators, SetMetadata, UseInterceptors } from '@nestjs/common';
|
|
2
|
+
import { WMS_MUTATION_EVENT_METADATA } from './wms-mutation-event.constants.js';
|
|
3
|
+
import { WmsMutationInterceptor } from './wms-mutation.interceptor.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Declares the operation event for a WMS root mutation and wires the
|
|
7
|
+
* {@link WmsMutationInterceptor} that runs before-hooks (veto) and flushes
|
|
8
|
+
* collected events post-commit. Apply alongside `@WmsOperation` on each mutation.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```ts
|
|
12
|
+
* @Mutation(() => ShipInventoryOrderDto)
|
|
13
|
+
* @WmsOperation({ resource: 'ship-inventory-order', action: 'ship' })
|
|
14
|
+
* @WmsMutationEvent({ event: 'ship-inventory-order.shipped' })
|
|
15
|
+
* ship(@Args() args: ShipArgs): Promise<boolean> { ... }
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
const WmsMutationEvent = (config) => applyDecorators(SetMetadata(WMS_MUTATION_EVENT_METADATA, config), UseInterceptors(WmsMutationInterceptor));
|
|
19
|
+
|
|
20
|
+
export { WmsMutationEvent };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type CallHandler, type ExecutionContext, type NestInterceptor } from '@nestjs/common';
|
|
2
|
+
import { Reflector } from '@nestjs/core';
|
|
3
|
+
import { WmsEventDispatcher } from '@rytass/wms-module-core';
|
|
4
|
+
import { type Observable } from 'rxjs';
|
|
5
|
+
/**
|
|
6
|
+
* Wraps each `@WmsMutationEvent` mutation in a dispatch scope:
|
|
7
|
+
* 1. runs the before-hook (throw -> veto, before any transaction);
|
|
8
|
+
* 2. executes the resolver (services collect stock events into the scope);
|
|
9
|
+
* 3. emits the operation event after the handler resolves (post-commit);
|
|
10
|
+
* 4. flushes the whole buffer to after-listeners + emitter bridge.
|
|
11
|
+
*
|
|
12
|
+
* When no hook/listener/bridge is configured it passes through untouched.
|
|
13
|
+
*/
|
|
14
|
+
export declare class WmsMutationInterceptor implements NestInterceptor {
|
|
15
|
+
private readonly reflector;
|
|
16
|
+
private readonly dispatcher;
|
|
17
|
+
constructor(reflector: Reflector, dispatcher: WmsEventDispatcher);
|
|
18
|
+
intercept(context: ExecutionContext, next: CallHandler): Promise<Observable<unknown>>;
|
|
19
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { __decorate, __metadata } from 'tslib';
|
|
2
|
+
import { Injectable } from '@nestjs/common';
|
|
3
|
+
import { Reflector } from '@nestjs/core';
|
|
4
|
+
import { GqlExecutionContext } from '@nestjs/graphql';
|
|
5
|
+
import { WmsEventDispatcher, runInDispatchScope, collectToScope } from '@rytass/wms-module-core';
|
|
6
|
+
import { firstValueFrom, of } from 'rxjs';
|
|
7
|
+
import { WMS_MUTATION_EVENT_METADATA } from './wms-mutation-event.constants.js';
|
|
8
|
+
|
|
9
|
+
const defaultResolveId = (result, args) => {
|
|
10
|
+
if (result &&
|
|
11
|
+
typeof result === 'object' &&
|
|
12
|
+
'id' in result &&
|
|
13
|
+
typeof result.id === 'string') {
|
|
14
|
+
return result.id;
|
|
15
|
+
}
|
|
16
|
+
const argId = args['id'];
|
|
17
|
+
return typeof argId === 'string' ? argId : null;
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Wraps each `@WmsMutationEvent` mutation in a dispatch scope:
|
|
21
|
+
* 1. runs the before-hook (throw -> veto, before any transaction);
|
|
22
|
+
* 2. executes the resolver (services collect stock events into the scope);
|
|
23
|
+
* 3. emits the operation event after the handler resolves (post-commit);
|
|
24
|
+
* 4. flushes the whole buffer to after-listeners + emitter bridge.
|
|
25
|
+
*
|
|
26
|
+
* When no hook/listener/bridge is configured it passes through untouched.
|
|
27
|
+
*/
|
|
28
|
+
let WmsMutationInterceptor = class WmsMutationInterceptor {
|
|
29
|
+
reflector;
|
|
30
|
+
dispatcher;
|
|
31
|
+
constructor(reflector, dispatcher) {
|
|
32
|
+
this.reflector = reflector;
|
|
33
|
+
this.dispatcher = dispatcher;
|
|
34
|
+
}
|
|
35
|
+
async intercept(context, next) {
|
|
36
|
+
const config = this.reflector.get(WMS_MUTATION_EVENT_METADATA, context.getHandler());
|
|
37
|
+
if (!config || !this.dispatcher.isActive()) {
|
|
38
|
+
return next.handle();
|
|
39
|
+
}
|
|
40
|
+
const args = GqlExecutionContext.create(context).getArgs() ??
|
|
41
|
+
{};
|
|
42
|
+
// Before-hook runs before any DB work; a throw vetoes with zero side effects.
|
|
43
|
+
await this.dispatcher.runBeforeHook({
|
|
44
|
+
event: config.event,
|
|
45
|
+
args,
|
|
46
|
+
executionContext: context,
|
|
47
|
+
});
|
|
48
|
+
const { result, events } = await runInDispatchScope(async () => {
|
|
49
|
+
const handlerResult = await firstValueFrom(next.handle());
|
|
50
|
+
const id = config.resolveId
|
|
51
|
+
? config.resolveId(handlerResult, args)
|
|
52
|
+
: defaultResolveId(handlerResult, args);
|
|
53
|
+
const operationEvent = {
|
|
54
|
+
name: config.event,
|
|
55
|
+
payload: { id, result: handlerResult },
|
|
56
|
+
};
|
|
57
|
+
collectToScope(operationEvent);
|
|
58
|
+
return handlerResult;
|
|
59
|
+
});
|
|
60
|
+
if (events) {
|
|
61
|
+
await this.dispatcher.flush(events);
|
|
62
|
+
}
|
|
63
|
+
return of(result);
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
WmsMutationInterceptor = __decorate([
|
|
67
|
+
Injectable(),
|
|
68
|
+
__metadata("design:paramtypes", [Reflector,
|
|
69
|
+
WmsEventDispatcher])
|
|
70
|
+
], WmsMutationInterceptor);
|
|
71
|
+
|
|
72
|
+
export { WmsMutationInterceptor };
|
package/lib/graphql.module.d.ts
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
|
-
import { DynamicModule } from '@nestjs/common';
|
|
2
|
-
import type
|
|
1
|
+
import { DynamicModule, type Provider } from '@nestjs/common';
|
|
2
|
+
import { type WmsModuleCoreModuleAsyncOptions, type WmsEventModuleOptions } from '@rytass/wms-module-core';
|
|
3
|
+
import { type WmsAuthModuleOptions } from './auth';
|
|
4
|
+
/**
|
|
5
|
+
* Options for `WmsModuleGraphqlModule.forRootAsync`. Extends the core module
|
|
6
|
+
* options with:
|
|
7
|
+
* - {@link WmsAuthModuleOptions.authChecker} — opt-in resolver authorization;
|
|
8
|
+
* - {@link WmsEventModuleOptions} — `beforeHooks` (veto) / `afterListeners`
|
|
9
|
+
* (post-commit notify) for the mutation event/hook system;
|
|
10
|
+
* - `eventEmitterBridge` — pass `wmsEventEmitterBridgeProvider` from
|
|
11
|
+
* `@rytass/wms-module-graphql/adapters/event-emitter` to also fan events out
|
|
12
|
+
* to `@nestjs/event-emitter` (`@OnEvent('wms.<event>')`).
|
|
13
|
+
*
|
|
14
|
+
* Everything is optional; with nothing configured the module behaves exactly as
|
|
15
|
+
* before (no events emitted, fully permissive).
|
|
16
|
+
*/
|
|
17
|
+
export type WmsModuleGraphqlModuleAsyncOptions = WmsModuleCoreModuleAsyncOptions & WmsAuthModuleOptions & WmsEventModuleOptions & {
|
|
18
|
+
readonly eventEmitterBridge?: Provider;
|
|
19
|
+
};
|
|
3
20
|
export declare class WmsModuleGraphqlModule {
|
|
4
|
-
static forRootAsync(options:
|
|
21
|
+
static forRootAsync(options: WmsModuleGraphqlModuleAsyncOptions): DynamicModule;
|
|
5
22
|
}
|
package/lib/graphql.module.js
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import { __decorate } from 'tslib';
|
|
2
2
|
import { Module } from '@nestjs/common';
|
|
3
|
-
import { WmsModuleCoreModule } from '@rytass/wms-module-core';
|
|
3
|
+
import { WmsModuleCoreModule, WMS_EVENT_OPTIONS, WmsEventDispatcher } from '@rytass/wms-module-core';
|
|
4
4
|
import './helpers/loader.service.js';
|
|
5
5
|
import { HelperModule } from './helpers/helper.module.js';
|
|
6
6
|
import './helpers/query-path.js';
|
|
7
|
+
import { WmsAuthGuard } from './auth/wms-auth.guard.js';
|
|
8
|
+
import { WMS_AUTH_OPTIONS } from './auth/wms-auth.types.js';
|
|
9
|
+
import { WmsMutationInterceptor } from './events/wms-mutation.interceptor.js';
|
|
7
10
|
import { LoaderMutation } from './mutations/loader.mutation.js';
|
|
8
11
|
import { LoaderQuery } from './queries/loader.query.js';
|
|
9
12
|
import { MaterialMutation } from './mutations/material.mutation.js';
|
|
@@ -108,12 +111,24 @@ const exportDependencies = [
|
|
|
108
111
|
];
|
|
109
112
|
let WmsModuleGraphqlModule = WmsModuleGraphqlModule_1 = class WmsModuleGraphqlModule {
|
|
110
113
|
static forRootAsync(options) {
|
|
111
|
-
const
|
|
114
|
+
const { authChecker, beforeHooks, afterListeners, eventEmitterBridge, ...coreOptions } = options;
|
|
115
|
+
const coreModule = WmsModuleCoreModule.forRootAsync(coreOptions);
|
|
116
|
+
const authOptions = { authChecker };
|
|
117
|
+
const eventOptions = { beforeHooks, afterListeners };
|
|
118
|
+
const providers = [
|
|
119
|
+
...exportDependencies,
|
|
120
|
+
WmsAuthGuard,
|
|
121
|
+
{ provide: WMS_AUTH_OPTIONS, useValue: authOptions },
|
|
122
|
+
{ provide: WMS_EVENT_OPTIONS, useValue: eventOptions },
|
|
123
|
+
WmsEventDispatcher,
|
|
124
|
+
WmsMutationInterceptor,
|
|
125
|
+
...(eventEmitterBridge ? [eventEmitterBridge] : []),
|
|
126
|
+
];
|
|
112
127
|
return {
|
|
113
128
|
module: WmsModuleGraphqlModule_1,
|
|
114
129
|
imports: [coreModule, HelperModule],
|
|
115
|
-
providers
|
|
116
|
-
exports: [...exportDependencies, HelperModule, coreModule],
|
|
130
|
+
providers,
|
|
131
|
+
exports: [...exportDependencies, WmsAuthGuard, HelperModule, coreModule],
|
|
117
132
|
};
|
|
118
133
|
}
|
|
119
134
|
};
|
|
@@ -5,6 +5,10 @@ import { AllocateInventoryOrderService, StockService } from '@rytass/wms-module-
|
|
|
5
5
|
import { AllocateInventoryOrderDto } from '../dto/allocate-inventory-order.dto.js';
|
|
6
6
|
import { CreateAllocateInventoryOrderArgs, UpdateAllocateInventoryOrderArgs } from '../inputs/create-allocate-inventory-order.args.js';
|
|
7
7
|
import { DeallocateOrderArgs } from '../inputs/deallocate-order.args.js';
|
|
8
|
+
import '../auth/wms-auth.guard.js';
|
|
9
|
+
import { WmsOperation } from '../auth/wms-operation.decorator.js';
|
|
10
|
+
import { WmsMutationEvent } from '../events/wms-mutation-event.decorator.js';
|
|
11
|
+
import '../events/wms-mutation.interceptor.js';
|
|
8
12
|
|
|
9
13
|
let AllocateInventoryOrderMutation = class AllocateInventoryOrderMutation {
|
|
10
14
|
allocateInventoryOrderService;
|
|
@@ -53,6 +57,8 @@ let AllocateInventoryOrderMutation = class AllocateInventoryOrderMutation {
|
|
|
53
57
|
};
|
|
54
58
|
__decorate([
|
|
55
59
|
Mutation(() => AllocateInventoryOrderDto),
|
|
60
|
+
WmsOperation({ resource: 'allocate-inventory-order', action: 'create' }),
|
|
61
|
+
WmsMutationEvent({ event: 'allocate-inventory-order.created' }),
|
|
56
62
|
__param(0, Args()),
|
|
57
63
|
__metadata("design:type", Function),
|
|
58
64
|
__metadata("design:paramtypes", [CreateAllocateInventoryOrderArgs]),
|
|
@@ -60,6 +66,8 @@ __decorate([
|
|
|
60
66
|
], AllocateInventoryOrderMutation.prototype, "createAllocateInventoryOrder", null);
|
|
61
67
|
__decorate([
|
|
62
68
|
Mutation(() => AllocateInventoryOrderDto),
|
|
69
|
+
WmsOperation({ resource: 'allocate-inventory-order', action: 'update' }),
|
|
70
|
+
WmsMutationEvent({ event: 'allocate-inventory-order.updated' }),
|
|
63
71
|
__param(0, Args()),
|
|
64
72
|
__metadata("design:type", Function),
|
|
65
73
|
__metadata("design:paramtypes", [UpdateAllocateInventoryOrderArgs]),
|
|
@@ -67,6 +75,8 @@ __decorate([
|
|
|
67
75
|
], AllocateInventoryOrderMutation.prototype, "updateAllocateInventoryOrder", null);
|
|
68
76
|
__decorate([
|
|
69
77
|
Mutation(() => Boolean),
|
|
78
|
+
WmsOperation({ resource: 'allocate-inventory-order', action: 'deallocate' }),
|
|
79
|
+
WmsMutationEvent({ event: 'allocate-inventory-order.deallocated' }),
|
|
70
80
|
__param(0, Args('items', { type: () => [DeallocateOrderArgs] })),
|
|
71
81
|
__metadata("design:type", Function),
|
|
72
82
|
__metadata("design:paramtypes", [Array]),
|
|
@@ -5,6 +5,10 @@ import { HoldInventoryOrderService, StockService } from '@rytass/wms-module-core
|
|
|
5
5
|
import { HoldInventoryOrderDto } from '../dto/hold-inventory-order.dto.js';
|
|
6
6
|
import { CreateHoldInventoryOrderArgs, UpdateHoldInventoryOrderArgs } from '../inputs/create-hold-inventory-order.args.js';
|
|
7
7
|
import { ReleaseOrderArgs } from '../inputs/release-order.args.js';
|
|
8
|
+
import '../auth/wms-auth.guard.js';
|
|
9
|
+
import { WmsOperation } from '../auth/wms-operation.decorator.js';
|
|
10
|
+
import { WmsMutationEvent } from '../events/wms-mutation-event.decorator.js';
|
|
11
|
+
import '../events/wms-mutation.interceptor.js';
|
|
8
12
|
|
|
9
13
|
let HoldInventoryOrderMutation = class HoldInventoryOrderMutation {
|
|
10
14
|
holdInventoryOrderService;
|
|
@@ -53,6 +57,8 @@ let HoldInventoryOrderMutation = class HoldInventoryOrderMutation {
|
|
|
53
57
|
};
|
|
54
58
|
__decorate([
|
|
55
59
|
Mutation(() => HoldInventoryOrderDto),
|
|
60
|
+
WmsOperation({ resource: 'hold-inventory-order', action: 'create' }),
|
|
61
|
+
WmsMutationEvent({ event: 'hold-inventory-order.created' }),
|
|
56
62
|
__param(0, Args()),
|
|
57
63
|
__metadata("design:type", Function),
|
|
58
64
|
__metadata("design:paramtypes", [CreateHoldInventoryOrderArgs]),
|
|
@@ -60,6 +66,8 @@ __decorate([
|
|
|
60
66
|
], HoldInventoryOrderMutation.prototype, "createHoldInventoryOrder", null);
|
|
61
67
|
__decorate([
|
|
62
68
|
Mutation(() => HoldInventoryOrderDto),
|
|
69
|
+
WmsOperation({ resource: 'hold-inventory-order', action: 'update' }),
|
|
70
|
+
WmsMutationEvent({ event: 'hold-inventory-order.updated' }),
|
|
63
71
|
__param(0, Args()),
|
|
64
72
|
__metadata("design:type", Function),
|
|
65
73
|
__metadata("design:paramtypes", [UpdateHoldInventoryOrderArgs]),
|
|
@@ -67,6 +75,8 @@ __decorate([
|
|
|
67
75
|
], HoldInventoryOrderMutation.prototype, "updateHoldInventoryOrder", null);
|
|
68
76
|
__decorate([
|
|
69
77
|
Mutation(() => Boolean),
|
|
78
|
+
WmsOperation({ resource: 'hold-inventory-order', action: 'release' }),
|
|
79
|
+
WmsMutationEvent({ event: 'hold-inventory-order.released' }),
|
|
70
80
|
__param(0, Args('items', { type: () => [ReleaseOrderArgs] })),
|
|
71
81
|
__metadata("design:type", Function),
|
|
72
82
|
__metadata("design:paramtypes", [Array]),
|
|
@@ -29,6 +29,10 @@ import { Mutation, Args } from '@nestjs/graphql';
|
|
|
29
29
|
import { CreateLoaderTypeArgs, UpdateLoaderTypeArgs } from '../inputs/create-loader-type.args.js';
|
|
30
30
|
import { CreateLoaderArgs, CreateLoadersArgs, UpdateLoaderArgs } from '../inputs/create-loader.args.js';
|
|
31
31
|
import { In } from 'typeorm';
|
|
32
|
+
import '../auth/wms-auth.guard.js';
|
|
33
|
+
import { WmsOperation } from '../auth/wms-operation.decorator.js';
|
|
34
|
+
import { WmsMutationEvent } from '../events/wms-mutation-event.decorator.js';
|
|
35
|
+
import '../events/wms-mutation.interceptor.js';
|
|
32
36
|
|
|
33
37
|
let LoaderMutation = class LoaderMutation {
|
|
34
38
|
loaderService;
|
|
@@ -64,6 +68,8 @@ let LoaderMutation = class LoaderMutation {
|
|
|
64
68
|
};
|
|
65
69
|
__decorate([
|
|
66
70
|
Mutation(() => [LoaderDto]),
|
|
71
|
+
WmsOperation({ resource: 'loader', action: 'create' }),
|
|
72
|
+
WmsMutationEvent({ event: 'loader.created' }),
|
|
67
73
|
__param(0, Args()),
|
|
68
74
|
__metadata("design:type", Function),
|
|
69
75
|
__metadata("design:paramtypes", [CreateLoaderArgs]),
|
|
@@ -71,6 +77,8 @@ __decorate([
|
|
|
71
77
|
], LoaderMutation.prototype, "createLoader", null);
|
|
72
78
|
__decorate([
|
|
73
79
|
Mutation(() => [LoaderDto]),
|
|
80
|
+
WmsOperation({ resource: 'loader', action: 'create' }),
|
|
81
|
+
WmsMutationEvent({ event: 'loader.batch-created' }),
|
|
74
82
|
__param(0, Args('items', { type: () => [CreateLoadersArgs] })),
|
|
75
83
|
__metadata("design:type", Function),
|
|
76
84
|
__metadata("design:paramtypes", [Array]),
|
|
@@ -78,6 +86,8 @@ __decorate([
|
|
|
78
86
|
], LoaderMutation.prototype, "createBatchLoader", null);
|
|
79
87
|
__decorate([
|
|
80
88
|
Mutation(() => LoaderDto),
|
|
89
|
+
WmsOperation({ resource: 'loader', action: 'update' }),
|
|
90
|
+
WmsMutationEvent({ event: 'loader.updated' }),
|
|
81
91
|
__param(0, Args()),
|
|
82
92
|
__metadata("design:type", Function),
|
|
83
93
|
__metadata("design:paramtypes", [UpdateLoaderArgs]),
|
|
@@ -85,6 +95,8 @@ __decorate([
|
|
|
85
95
|
], LoaderMutation.prototype, "updateLoader", null);
|
|
86
96
|
__decorate([
|
|
87
97
|
Mutation(() => LoaderTypeDto),
|
|
98
|
+
WmsOperation({ resource: 'loader', action: 'create' }),
|
|
99
|
+
WmsMutationEvent({ event: 'loader.type-created' }),
|
|
88
100
|
__param(0, Args()),
|
|
89
101
|
__metadata("design:type", Function),
|
|
90
102
|
__metadata("design:paramtypes", [CreateLoaderTypeArgs]),
|
|
@@ -92,6 +104,8 @@ __decorate([
|
|
|
92
104
|
], LoaderMutation.prototype, "createLoaderType", null);
|
|
93
105
|
__decorate([
|
|
94
106
|
Mutation(() => LoaderTypeDto),
|
|
107
|
+
WmsOperation({ resource: 'loader', action: 'update' }),
|
|
108
|
+
WmsMutationEvent({ event: 'loader.type-updated' }),
|
|
95
109
|
__param(0, Args()),
|
|
96
110
|
__metadata("design:type", Function),
|
|
97
111
|
__metadata("design:paramtypes", [UpdateLoaderTypeArgs]),
|
|
@@ -4,6 +4,10 @@ import { Mutation, Args } from '@nestjs/graphql';
|
|
|
4
4
|
import { LocationService } from '@rytass/wms-module-core';
|
|
5
5
|
import { LocationDto } from '../dto/location.dto.js';
|
|
6
6
|
import { CreateLocationArgs, UpdateLocationArgs } from '../inputs/create-location.args.js';
|
|
7
|
+
import '../auth/wms-auth.guard.js';
|
|
8
|
+
import { WmsOperation } from '../auth/wms-operation.decorator.js';
|
|
9
|
+
import { WmsMutationEvent } from '../events/wms-mutation-event.decorator.js';
|
|
10
|
+
import '../events/wms-mutation.interceptor.js';
|
|
7
11
|
|
|
8
12
|
let LocationMutation = class LocationMutation {
|
|
9
13
|
locationService;
|
|
@@ -19,6 +23,8 @@ let LocationMutation = class LocationMutation {
|
|
|
19
23
|
};
|
|
20
24
|
__decorate([
|
|
21
25
|
Mutation(() => LocationDto),
|
|
26
|
+
WmsOperation({ resource: 'location', action: 'create' }),
|
|
27
|
+
WmsMutationEvent({ event: 'location.created' }),
|
|
22
28
|
__param(0, Args()),
|
|
23
29
|
__metadata("design:type", Function),
|
|
24
30
|
__metadata("design:paramtypes", [CreateLocationArgs]),
|
|
@@ -26,6 +32,8 @@ __decorate([
|
|
|
26
32
|
], LocationMutation.prototype, "createLocation", null);
|
|
27
33
|
__decorate([
|
|
28
34
|
Mutation(() => LocationDto),
|
|
35
|
+
WmsOperation({ resource: 'location', action: 'update' }),
|
|
36
|
+
WmsMutationEvent({ event: 'location.updated' }),
|
|
29
37
|
__param(0, Args()),
|
|
30
38
|
__metadata("design:type", Function),
|
|
31
39
|
__metadata("design:paramtypes", [UpdateLocationArgs]),
|