@rytass/wms-module-graphql 0.2.0 → 0.4.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.
Files changed (53) hide show
  1. package/README.md +75 -29
  2. package/adapters/member-base.d.ts +35 -0
  3. package/adapters/member-base.js +47 -0
  4. package/docs/README.md +6 -3
  5. package/docs/api-reference.md +143 -11
  6. package/docs/authorization.md +349 -0
  7. package/docs/integration-guide.md +155 -84
  8. package/index.cjs.js +177 -6
  9. package/index.d.ts +2 -0
  10. package/index.js +4 -0
  11. package/lib/auth/host-skip.constant.d.ts +27 -0
  12. package/lib/auth/host-skip.constant.js +29 -0
  13. package/lib/auth/index.d.ts +4 -0
  14. package/lib/auth/wms-auth.guard.d.ts +19 -0
  15. package/lib/auth/wms-auth.guard.js +41 -0
  16. package/lib/auth/wms-auth.types.d.ts +36 -0
  17. package/lib/auth/wms-auth.types.js +6 -0
  18. package/lib/auth/wms-operation.decorator.d.ts +23 -0
  19. package/lib/auth/wms-operation.decorator.js +29 -0
  20. package/lib/graphql.module.d.ts +8 -1
  21. package/lib/graphql.module.js +12 -3
  22. package/lib/helpers/create-wms-loaders.d.ts +3 -1
  23. package/lib/helpers/create-wms-loaders.js +5 -3
  24. package/lib/mutations/allocate-inventory-order.mutation.js +5 -0
  25. package/lib/mutations/hold-inventory-order.mutation.js +5 -0
  26. package/lib/mutations/loader.mutation.js +7 -0
  27. package/lib/mutations/location.mutation.js +4 -0
  28. package/lib/mutations/material.mutation.js +4 -0
  29. package/lib/mutations/merge-batch.mutation.js +3 -0
  30. package/lib/mutations/quality-inspection-order.mutation.js +3 -0
  31. package/lib/mutations/receive-inventory-order.mutation.js +5 -0
  32. package/lib/mutations/reclassify-inventory-order.mutation.js +3 -0
  33. package/lib/mutations/scrape-order.mutation.js +3 -0
  34. package/lib/mutations/ship-inventory-order.mutation.js +6 -0
  35. package/lib/mutations/split-batch.mutation.js +3 -0
  36. package/lib/mutations/transfer-customer-order.mutation.js +5 -0
  37. package/lib/mutations/transfer-order.mutation.js +6 -0
  38. package/lib/mutations/transfer-vendor-order.mutation.js +5 -0
  39. package/lib/mutations/warehouse-map.mutation.js +3 -0
  40. package/lib/queries/allocate-inventory-order.query.js +3 -0
  41. package/lib/queries/batch.query.js +3 -0
  42. package/lib/queries/hold-inventory-order.query.js +3 -0
  43. package/lib/queries/loader.query.js +8 -0
  44. package/lib/queries/location.query.js +4 -0
  45. package/lib/queries/material.query.js +4 -0
  46. package/lib/queries/recieve-inventory-order.query.js +4 -0
  47. package/lib/queries/sale-order.query.js +4 -0
  48. package/lib/queries/ship-inventory-order.query.js +4 -0
  49. package/lib/queries/stock.query.js +7 -0
  50. package/lib/queries/transfer-order.query.js +4 -0
  51. package/lib/queries/unit.query.js +3 -0
  52. package/lib/queries/warehouse-map.query.js +3 -0
  53. package/package.json +5 -1
@@ -40,8 +40,8 @@ WMS is packaged as three layered npm libraries:
40
40
  v
41
41
  +-------------------------------+
42
42
  | wms-module-graphql | Resolvers, DTOs, DataLoader helpers
43
- | (one forRootAsync wires up | ---------------------------------------
44
- | the entire GraphQL surface) | Wires the schema, depends on core for
43
+ | (composable providers + your | ---------------------------------------
44
+ | own GraphQLModule.forRoot) | Exposes resolvers; depends on core for
45
45
  +--------------+----------------+ entities + services.
46
46
  v
47
47
  +-------------------------------+
@@ -138,13 +138,18 @@ bootstrap();
138
138
 
139
139
  ### 4.2 `apps/api/src/app/app.module.ts`
140
140
 
141
- The entire backend domain is wired in a single `forRootAsync` call on `WmsModuleGraphqlModule`. The module cascades into `WmsModuleCoreModule` automatically — the consumer **does not** import `wms-module-core` directly.
141
+ Two registrations: `WmsModuleGraphqlModule.forRootAsync(...)` wires the WMS resolvers, entities, services and `LoaderService`; your own `GraphQLModule.forRootAsync(...)` starts Apollo Server and owns the context factory. The two participate in **one** schema — NestJS's schema builder discovers every `@Resolver()` provider across all imported modules.
142
142
 
143
143
  ```ts
144
144
  import { Module } from '@nestjs/common';
145
145
  import { ConfigModule, ConfigService } from '@nestjs/config';
146
146
  import { TypeOrmModule, TypeOrmModuleOptions } from '@nestjs/typeorm';
147
- import { WmsModuleGraphqlModule } from '@rytass/wms-module-graphql';
147
+ import { GraphQLModule } from '@nestjs/graphql';
148
+ import {
149
+ WmsModuleGraphqlModule,
150
+ LoaderService,
151
+ createWmsLoaders,
152
+ } from '@rytass/wms-module-graphql';
148
153
  import { ApolloDriver } from '@nestjs/apollo';
149
154
  import {
150
155
  ApolloServerPluginLandingPageLocalDefault,
@@ -173,40 +178,46 @@ import { resolve } from 'path';
173
178
  }),
174
179
  }),
175
180
 
176
- // WMS GraphQL - one call wires the entire domain
177
- WmsModuleGraphqlModule.forRootAsync(
178
- {
179
- useFactory: async () => ({}),
180
- // Module-init seed values; the keys you pass here are created
181
- // on first boot for `location`, `loaderType`, and `loader`.
182
- defaultLoaderSerialId: 'DEFAULT',
183
- defaultLoaderTypeKey: 'DEFAULT',
184
- defaultLocationKey: 'DEFAULT',
185
- },
186
- {
187
- driver: ApolloDriver,
188
- useFactory: async () => ({
189
- playground: false,
190
- introspection: true,
191
- cors: true,
192
- csrfPrevention: false,
193
- plugins: [ApolloServerPluginLandingPageLocalDefault()],
194
- autoTransformHttpErrors: true,
195
- autoSchemaFile: resolve(process.cwd(), 'schema.gql'),
181
+ // WMS resolvers + LoaderService — no Apollo Server is started here
182
+ WmsModuleGraphqlModule.forRootAsync({
183
+ useFactory: async () => ({}),
184
+ // Module-init seed values; the keys you pass here are created
185
+ // on first boot for `location`, `loaderType`, and `loader`.
186
+ defaultLoaderSerialId: 'DEFAULT',
187
+ defaultLoaderTypeKey: 'DEFAULT',
188
+ defaultLocationKey: 'DEFAULT',
189
+ }),
190
+
191
+ // YOUR Apollo / Schema / Context registration
192
+ GraphQLModule.forRootAsync({
193
+ driver: ApolloDriver,
194
+ inject: [LoaderService],
195
+ useFactory: (loaderService: LoaderService) => ({
196
+ playground: false,
197
+ introspection: true,
198
+ cors: true,
199
+ csrfPrevention: false,
200
+ plugins: [ApolloServerPluginLandingPageLocalDefault()],
201
+ autoTransformHttpErrors: true,
202
+ autoSchemaFile: resolve(process.cwd(), 'schema.gql'),
203
+ context: ({ req }) => ({
204
+ ...createWmsLoaders(loaderService), // per-request DataLoaders
205
+ // your own context fields — fully preserved
206
+ user: req.user,
196
207
  }),
197
- },
198
- ),
208
+ }),
209
+ }),
199
210
  ],
200
211
  })
201
212
  export class AppModule {}
202
213
  ```
203
214
 
204
- What you get from this single `forRootAsync`:
215
+ What this registration wires:
205
216
 
206
217
  - 50 TypeORM entities (material, batch, loader, location, every order kind, change logs, signatures, etc.)
207
218
  - ~100 GraphQL types
208
219
  - ~65 queries and mutations
209
- - DataLoader N+1 prevention across every resolveField
220
+ - DataLoader N+1 prevention across every `resolveField` (via the `loaders` key in context)
210
221
  - Default seed rows (DEFAULT location, DEFAULT loaderType, DEFAULT loader) on first boot
211
222
 
212
223
  **`forRootAsync` TypeScript signature**:
@@ -214,21 +225,16 @@ What you get from this single `forRootAsync`:
214
225
  ```ts
215
226
  WmsModuleGraphqlModule.forRootAsync(
216
227
  options: WmsModuleCoreModuleAsyncOptions,
217
- gqlOptions: GqlModuleAsyncOptions<ApolloDriverConfig>,
218
228
  ): DynamicModule
219
229
  ```
220
230
 
221
- > Note the **two positional arguments**. A common mistake is to pass a single options object
222
- > (matching NestJS's typical convention) which produces a misleading
223
- > `TypeError: Cannot read properties of undefined (reading 'imports')` from inside the lib.
224
- > The first arg configures the WMS core module (seed defaults, async factory, etc.); the
225
- > second arg configures the underlying NestJS GraphQL module (driver, autoSchemaFile, plugins).
231
+ A single argument — the WMS core module options (seed defaults, async factory). All GraphQL-server concerns (driver, schema file, context factory, plugins) live in your own `GraphQLModule.forRootAsync(...)` next to it.
226
232
 
227
- See [API Reference - wms-module-graphql](./api-reference.md) for the full `forRootAsync` signature.
233
+ See [API Reference - wms-module-graphql](./api-reference.md) for the full export catalog including `LoaderService`, `createWmsLoaders`, `LoaderType<T>` and `WmsLoaders`.
228
234
 
229
235
  ### Adding your own resolvers alongside WMS
230
236
 
231
- `WmsModuleGraphqlModule` calls `GraphQLModule.forRootAsync(...)` internally — that registration is workspace-global, so you cannot register a second `GraphQLModule` (see §13.1). What you **can** do is put your own resolvers in **sibling NestJS modules** imported in `AppModule`:
237
+ Your `GraphQLModule.forRootAsync(...)` registration **is** the single workspace-wide GraphQL module — no constraint from `WmsModuleGraphqlModule` (the lib intentionally does **not** call `GraphQLModule.forRoot` internally). Put your resolvers in normal NestJS modules and import them next to `WmsModuleGraphqlModule`:
232
238
 
233
239
  ```ts
234
240
  // apps/api/src/modules/posts/posts.module.ts
@@ -246,14 +252,14 @@ export class PostsModule {}
246
252
  // apps/api/src/app/app.module.ts
247
253
  @Module({
248
254
  imports: [
249
- /* ... TypeOrm, Config, WmsModuleGraphqlModule ... */
255
+ /* ... TypeOrm, Config, WmsModuleGraphqlModule, GraphQLModule ... */
250
256
  PostsModule, // your sibling module
251
257
  ],
252
258
  })
253
259
  export class AppModule {}
254
260
  ```
255
261
 
256
- NestJS's GraphQL module discovers every `@Resolver()` class from every imported module via DI and merges them into the **same** schema. So your `Post`/`User` resolvers coexist in one schema with the WMS resolvers automatically — no extra wiring needed. The §13.1 "one GraphQLModule" constraint only forbids a second `GraphQLModule.forRoot(...)` registration, not additional resolvers.
262
+ NestJS's GraphQL module discovers every `@Resolver()` class from every imported module via DI and merges them into the **same** schema. So your `Post`/`User` resolvers coexist in one schema with the WMS resolvers automatically — no extra wiring needed.
257
263
 
258
264
  ### 4.3 `.env`
259
265
 
@@ -735,7 +741,7 @@ The full list of generated `Document` constants and their types is in
735
741
 
736
742
  ## 8. Authentication / authorization
737
743
 
738
- The lib **does not** ship an auth system. The page components assume they render in an authenticated context — they call queries on mount without checking session state.
744
+ 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).
739
745
 
740
746
  ### 8.1 Stub for demos
741
747
 
@@ -823,7 +829,43 @@ Replace the `AuthGuard` stub with your own logic that:
823
829
  2. Provides member identity to GraphQL via the Apollo client's `context` link.
824
830
  3. Handles 401 responses (logout / redirect).
825
831
 
826
- The lib's resolvers do not enforce auth themselves. If you need RBAC, layer your own guard/decorator on top of the resolvers or apply NestJS-level guards globally in `app.module.ts`.
832
+ ### 8.5 Backend authorization (resolver-level)
833
+
834
+ > 📖 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).
835
+
836
+ 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.
837
+
838
+ > ⚠️ **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.**
839
+
840
+ #### Wire it up (member-base / Casbin)
841
+
842
+ ```ts
843
+ // app.module.ts
844
+ import { WmsModuleGraphqlModule } from '@rytass/wms-module-graphql';
845
+ import { createMemberBaseWmsAuthChecker } from '@rytass/wms-module-graphql/adapters/member-base';
846
+
847
+ WmsModuleGraphqlModule.forRootAsync({
848
+ // ...existing core options (defaultLoaderSerialId, useFactory, etc.)...
849
+ authChecker: createMemberBaseWmsAuthChecker({
850
+ // Map WMS resource/action onto your Casbin [subject, action] tuples.
851
+ // Return multiple tuples to mean "allow if ANY passes" (OR semantics).
852
+ map: ({ resource, action }) => [[`wms:${resource}`, action]],
853
+ // e.g. material:read -> enforce(memberId, domain, 'wms:material', 'read')
854
+ }),
855
+ });
856
+ ```
857
+
858
+ 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).
859
+
860
+ #### Other auth systems
861
+
862
+ `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`.
863
+
864
+ #### No global guard at all
865
+
866
+ 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`.
867
+
868
+ 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`).
827
869
 
828
870
  ---
829
871
 
@@ -960,37 +1002,59 @@ This section covers the real-world scenarios where the WMS modules are added to
960
1002
  that already has its own NestJS backend, GraphQL setup, or Next.js frontend. The greenfield
961
1003
  setup in sections 4-7 is the baseline; read this section for the delta.
962
1004
 
963
- > **Scope**: 0.1.x. Many limitations listed here are known gaps — roadmap notes are included
964
- > where improvements are planned.
1005
+ > **Scope**: 0.3.0+. Earlier 0.1.x / 0.2.0 limitations around `GraphQLModule` co-existence have
1006
+ > been removed by the user-driven composition refactor — see §13.1.
965
1007
 
966
1008
  ### 13.1 Coexisting with an existing `GraphQLModule`
967
1009
 
968
- **Status**: Not supported in 0.1.x without forking the library.
969
-
970
- `WmsModuleGraphqlModule.forRootAsync(...)` internally calls `GraphQLModule.forRootAsync(...)`.
971
- NestJS does not allow two `GraphQLModule` registrations in the same application — the second
972
- registration throws `Error: Cannot register 2 GraphQL modules`. If your host app already
973
- imports `GraphQLModule`, you cannot simply add `WmsModuleGraphqlModule` to the same
974
- `imports: [...]` array.
1010
+ **Status**: Fully supported since 0.3.0.
975
1011
 
976
- **Recommended workarounds** (in order of practicality):
1012
+ `WmsModuleGraphqlModule.forRootAsync(...)` only registers providers (resolvers, `LoaderService`,
1013
+ core module) — it does **not** call `GraphQLModule.forRoot` internally. The host application
1014
+ registers a **single** `GraphQLModule.forRootAsync(...)` of its own. NestJS's schema builder
1015
+ walks the DI container at boot, collects every `@Resolver()` class from every imported module,
1016
+ and merges them into one schema.
977
1017
 
978
- 1. **Separate NestJS process** — run the WMS API as its own NestJS server on a different port
979
- (e.g. 7103) and have the host API proxy or federate as needed. This is the architecture
980
- used by `wms-standalone` and is the safest path.
1018
+ This means a host app with an existing GraphQL server just adds `WmsModuleGraphqlModule` next to
1019
+ its existing `GraphQLModule.forRootAsync(...)`. To preserve N+1 prevention for the WMS resolvers,
1020
+ the host must inject `LoaderService` into its own context factory and call `createWmsLoaders(...)`
1021
+ per request:
981
1022
 
982
- 2. **NestJS sub-app (monorepo)** — if you use the Nx or NestJS CLI monorepo pattern, you can
983
- host the WMS app as a separate application inside the same repo that shares code with your
984
- existing app but runs as a distinct process.
1023
+ ```ts
1024
+ import {
1025
+ WmsModuleGraphqlModule,
1026
+ LoaderService,
1027
+ createWmsLoaders,
1028
+ } from '@rytass/wms-module-graphql';
985
1029
 
986
- 3. **Fork and merge** — advanced consumers can fork `libs/graphql` and extract the resolver
987
- registrations, removing the internal `GraphQLModule.forRootAsync(...)` call. The host app
988
- then registers a single `GraphQLModule` that includes the WMS resolver classes. This requires
989
- maintaining a fork and is not recommended unless you intend to take full ownership of the lib.
1030
+ @Module({
1031
+ imports: [
1032
+ // ... your existing modules ...
1033
+ WmsModuleGraphqlModule.forRootAsync({
1034
+ useFactory: async () => ({}),
1035
+ defaultLoaderSerialId: 'DEFAULT',
1036
+ defaultLoaderTypeKey: 'DEFAULT',
1037
+ defaultLocationKey: 'DEFAULT',
1038
+ }),
1039
+ GraphQLModule.forRootAsync({
1040
+ driver: ApolloDriver,
1041
+ inject: [LoaderService, /* your existing deps */],
1042
+ useFactory: (loaderService: LoaderService /* , ... */) => ({
1043
+ autoSchemaFile: 'schema.gql',
1044
+ context: ({ req }) => ({
1045
+ ...createWmsLoaders(loaderService), // WMS DataLoader trio
1046
+ user: req.user, // your existing context fields
1047
+ // ... any other context shape you already had ...
1048
+ }),
1049
+ }),
1050
+ }),
1051
+ ],
1052
+ })
1053
+ export class AppModule {}
1054
+ ```
990
1055
 
991
- **Tied to §13.1**: the `/graphql` endpoint name conflict (see §13.14 for GraphQL type name conflicts) is also
992
- fundamentally blocked by this same single-GraphQLModule constraint when running both APIs in
993
- the same process.
1056
+ **Type name conflicts**: still a separate concern — see §13.14. The shared-schema model means
1057
+ your `type Material`, `type Location`, etc. must not collide with the WMS counterparts.
994
1058
 
995
1059
  ---
996
1060
 
@@ -1128,26 +1192,34 @@ export class AppModule {}
1128
1192
 
1129
1193
  This applies your guard to every resolver, including WMS resolvers.
1130
1194
 
1131
- **Option B**: Read auth identity via `context.req.user` in custom resolvers. The Express `req`
1132
- object is spread directly into the GraphQL context by the lib's internal DataLoader injection
1133
- logic. Any field set on `req` by a NestJS guard or Passport strategy (e.g. `req.user`) is
1134
- therefore accessible from resolvers as `ctx.req.user`.
1195
+ **Option B**: Surface the authenticated user through your **own** GraphQL context factory. Since 0.3.0 you own the entire `GraphQLModule.forRootAsync(...)` registration, including `context`. Both `context.user` (returned directly from your factory) and `context.req.user` (set by a guard / Passport on Express) work — pick whichever your resolvers prefer:
1135
1196
 
1136
- > **Why returning `context` from `gqlOptions.useFactory` does not work**: the lib's
1137
- > internal context factory replaces any `context` key you return from `gqlOptions.useFactory`,
1138
- > but it spreads the raw `contextArg` (the `{ req, res }` provided by Apollo Server). So:
1139
- >
1140
- > | Approach | Works? |
1141
- > |----------------------------------------------------|--------|
1142
- > | Return `context: () => ({ user })` from useFactory | ❌ overridden |
1143
- > | Populate `req.user` via NestJS guard / Passport | ✅ access via `context.req.user` |
1144
- >
1145
- > Always populate identity onto `req` (via a guard) and read it as `context.req.user` in
1146
- > your resolvers. The resulting GraphQL context is `{ req, res, loaders }` — `req.user` IS
1147
- > accessible as long as a guard or middleware has set it on the request object.
1197
+ ```ts
1198
+ GraphQLModule.forRootAsync({
1199
+ driver: ApolloDriver,
1200
+ inject: [LoaderService],
1201
+ useFactory: (loaderService: LoaderService) => ({
1202
+ autoSchemaFile: 'schema.gql',
1203
+ context: ({ req }) => ({
1204
+ ...createWmsLoaders(loaderService), // WMS DataLoader trio
1205
+ user: req.user, // directly accessible as ctx.user
1206
+ // ...any other fields you want on context
1207
+ }),
1208
+ }),
1209
+ })
1210
+ ```
1211
+
1212
+ | Approach | Works? |
1213
+ |---------------------------------------------------------|--------|
1214
+ | Return `context: () => ({ user, ...createWmsLoaders(svc) })` | ✅ user fully controls context |
1215
+ | Populate `req.user` via NestJS guard / Passport | ✅ access via `context.req.user` |
1216
+
1217
+ > **0.1.x behavior gone**: earlier releases of this lib silently replaced any `context` factory
1218
+ > you supplied because `WmsModuleGraphqlModule` registered Apollo Server itself. Since 0.3.0,
1219
+ > the lib only exposes providers and your `GraphQLModule.forRootAsync(...)` is the single
1220
+ > registration — there is no internal override anymore.
1148
1221
 
1149
- To access the authenticated user in a **custom** resolver, populate `req.user` in a guard
1150
- or middleware and read it from `ctx.req.user`:
1222
+ To access the authenticated user in a **custom** resolver, either read it directly from `ctx.user` (if you put it there in your factory) or from `ctx.req.user` (if a guard set it on the request):
1151
1223
 
1152
1224
  ```ts
1153
1225
  // auth.guard.ts — populate req.user before the resolver runs
@@ -1631,8 +1703,7 @@ The viable options are the same as those described in §13.1:
1631
1703
  3. **Wait for a future major release** — a type prefix configuration option is a candidate
1632
1704
  for a future major version that introduces namespace support.
1633
1705
 
1634
- This is the same fundamental single-`GraphQLModule` constraint discussed in §13.1. Running
1635
- both APIs in the same process is not supported in 0.1.x.
1706
+ Unlike the §13.1 `GraphQLModule` co-existence story (fixed in 0.3.0), the type-name collision is a **shared-schema** problem: every `@Resolver()` provider in the DI container, WMS or host-app, contributes to the same `GraphQLSchema` instance. Renaming the host app's types away from WMS-occupied names is the only fix without splitting processes.
1636
1707
 
1637
1708
 
1638
1709
  ## 14. Reference implementation (`wms-standalone`)
@@ -1647,7 +1718,7 @@ wms-standalone/
1647
1718
  api/
1648
1719
  src/
1649
1720
  main.ts 17 LOC NestJS bootstrap
1650
- app/app.module.ts 52 LOC WmsModuleGraphqlModule.forRootAsync
1721
+ app/app.module.ts 60 LOC WmsModuleGraphqlModule + GraphQLModule.forRootAsync
1651
1722
  .env 6 LOC PG credentials
1652
1723
  package.json
1653
1724
  client/