Overview
Backend cross-plugin reuse is a published package contract plus DI scoping. Export stable classes from backend/src/export.ts, build and publish @quan-erp-plugins/<name>-backend, then consumers install the package and inject with the provider plugin’s metadata.name.
- Never import another plugin’s backend/src/ directly — only the published package
- Package name: @quan-erp-plugins/<plugin-folder>-backend
- DI scope: @Inject(ServiceClass, "provider-plugin-name") — second arg matches module.metadata.json name
- Declare pluginDependencies so the provider loads before the consumer
1. Define the public surface (export.ts)
Create or update backend/src/export.ts. Everything re-exported here becomes the public API other plugins may depend on. Export only stable services, selected entities/DTOs, and types — not internal helpers or WIP modules.
- Use .js extensions in relative re-exports (ESM build)
- Prefer exporting @Service classes consumers will inject
- Entity exports are optional — only when another plugin must type against them
- Keep export.ts small; deep paths stay private
1// plugins/fleet-management/backend/src/export.ts
2
3export * from "./feature/driver/driver.service.js";
4export * from "./feature/driver/driver.controller.js";
5export * from "./schema/driver/driver.entity.js";2. Build and publish
The plugin backend package.json ships scripts that Rollup-compile export.ts into dist/ and publish under @quan-erp-plugins/.
- build:export — compile export.ts + dependencies into dist/
- release:beta — publish to the internal / self-hosted npm registry with the beta tag
- Align @quan-erp/* versions with the base stack before publishing
- Consumers need a valid ~/.npmrc for the same registry
cd plugins/fleet-management/backend
npm run build:export
npm run release:beta3. Consumer — install the package
In the consumer plugin’s backend package, install only what you need. For backend services, install the -backend package (you do not need the frontend package).
- Add the dependency to plugins/<consumer>/backend/package.json
- Pin a compatible version range that matches what you tested
- Do not copy service source files into the consumer plugin
cd plugins/my-plugin/backend
npm install @quan-erp-plugins/fleet-management-backend4. Consumer — inject with plugin scope
Use @Inject with the provider plugin’s metadata.name as the second argument so DI resolves outside the current module scope.
- Second argument MUST equal the provider’s module.metadata.json "name" (e.g. "fleet-management")
- Omit the second argument only for services registered in the same plugin
- Builtin platform services use ContainerRegistryManager.BUILTIN_PLUGIN instead of a plugin name
- Call cross-plugin work from @OnAllModuleLoaded when you need every dependency module finished @OnInit
1import { Service, Inject } from "@quan-erp/shared-backend-core";
2import { FleetDriverManagementService } from "@quan-erp-plugins/fleet-management-backend";
3
4@Service()
5export class MyService {
6 @Inject(FleetDriverManagementService, "fleet-management")
7 driverService: FleetDriverManagementService;
8
9 async doSomething() {
10 const drivers = await this.driverService.list();
11 return drivers;
12 }
13}5. Declare pluginDependencies
The consumer must list the provider in module.metadata.json so the host loads the provider first and refuses to start if it is missing or incompatible.
- Keys are provider plugin names (folder / metadata.name), not npm package names
- Values are semver ranges for the provider’s pluginVersion
- Without this entry, DI may fail or load order may be wrong
1{
2 "name": "my-plugin",
3 "type": "",
4 "pluginVersion": "1.0.0",
5 "description": "Depends on fleet-management backend APIs",
6 "moduleEntryObject": "Module",
7 "requiredBasedVersion": "1.0.0",
8 "pluginDependencies": {
9 "fleet-management": "^1.0.0"
10 }
11}Checklist
- Provider: backend/src/export.ts re-exports public services / types
- Provider: npm run build:export && npm run release:beta (or your registry publish flow)
- Consumer: npm install @quan-erp-plugins/<name>-backend
- Consumer: pluginDependencies includes the provider name + version range
- Consumer: @Inject(Service, "<provider-metadata-name>")
- Both plugins installed in the ERP and on a matching @quan-erp/* version line
Common failures
- Importing plugins/<other>/backend/src — breaks packaging and load order; use the published package
- Wrong @Inject plugin name — must match metadata.name, not the npm package name
- Missing pluginDependencies — provider may load after consumer or be absent
- Forgot build:export / publish — consumer installs an empty or stale package
- Version skew — provider package and installed pluginVersion out of sync with requiredBasedVersion
- Using the service before @OnAllModuleLoaded when the call needs every module initialized