# Cross-Module Dependency

## Overview

Each module's `defineModule()` returns `{ db, commands, queries }`. Other modules can reference any of these through the return type. Use `import type` and relative paths — all modules live in the same package.

**Never derive a dependency's shape locally as `ReturnType<typeof defineModule>`.** `defineModule` is generic over each field-set type parameter (e.g. `IF extends Record<string, TailorAnyDBField> = EmptyFields`). Applying `ReturnType<>` to the function without supplying those type arguments resolves them against their `extends` constraint (`Record<string, TailorAnyDBField>`) instead of their `EmptyFields` default — silently widening field-keyed types derived from it (e.g. the hooks `validate()` callback's `issues()` parameter to plain `string`). A consumer that later passes a table built with real custom fields into that dependency's params can then fail with a confusing `TS2719`/`TS2322` ("two different types with this name exist, but they are unrelated"), even though the values are structurally compatible.

Instead, every module exports its own `EmptyFields`-instantiated type alias from `module.ts` (named `<ModuleName>Module`, e.g. `PrimitivesModule`), and dependents import that alias directly:

```typescript
// primitives/module.ts — exported once by the module itself
/** EmptyFields-instantiated alias — use instead of `ReturnType<typeof defineModule>`, which widens field generics to their constraint. */
export type PrimitivesModule = ReturnType<
  typeof defineModule<EmptyFields, EmptyFields, EmptyFields, EmptyFields>
>;
```

```typescript
// consuming module — import the alias, don't re-derive it
import type { PrimitivesModule } from "../primitives";

type PrimitivesDB = PrimitivesModule["db"];
type PrimitivesCommands = PrimitivesModule["commands"];
type PrimitivesQueries = PrimitivesModule["queries"];
```

Re-export the alias from the module's `index.ts` alongside `defineModule` so consumers can import it from the module's public entry point.

## DB Type Injection (`db/*.ts`)

Inject another module's DB type to create a foreign-key relation. **Only cross-module types use this pattern** — same-module types are imported directly (see [db-relations.md](db-relations.md#intra-module-foreign-keys)).

- Accept the external type via `CreateTypeParams` as an **optional** param (e.g., `unitType?: TailorAnyDBType`). It is optional only so the codegen `export const` below can build the type standalone; real callers always inject it via `DefineModuleParams` (see Module Wiring), so the relation is always present at deploy.
- Build the FK column once and attach the relation **only when the type is injected** — at standalone codegen (no injection) the column stays a plain uuid with no cross-module reference:
  ```typescript
  // db/item.ts
  const unitId = db.uuid().description("Foreign key to Unit");
  // ...inside db.table({ ... }):
  unitId: params.unitType
    ? unitId.relation({ type: "n-1", toward: { type: params.unitType }, backward: "items" })
    : unitId,
  ```
- Export the codegen instance with **no** cross-module type:
  ```typescript
  export const item = createItemType({});
  ```

## Command References

A module can accept another module's commands as a dependency and call them internally:

- Accept commands via `DefineModuleParams` as dependencies
- Derive the type from the source module's return type

```typescript
// Types
type ConvertQuantity = PrimitivesCommands["convertQuantity"];

// DefineModuleParams
export interface DefineModuleParams {
  primitives: {
    commands: {
      convertQuantity: ConvertQuantity;
    };
  };
}

// Usage in a command
const convertQuantity = params.primitives.commands.convertQuantity;
const result = await convertQuantity(db, input, ctx);
```

## Query References

Same pattern as commands — accept queries via `DefineModuleParams`. See [CQRS read rule](../../erp-kit-shared/references/commands.md#command-side-reads-cqrs-separation) for when commands use injected queries vs inline reads.

```typescript
type ConvertAmount = PrimitivesQueries["convertAmount"];

export interface DefineModuleParams {
  primitives: {
    queries: {
      convertAmount: ConvertAmount;
    };
  };
}
```

## Module Wiring (`module.ts`)

Group all external dependencies (db, commands, queries) under a named key in `DefineModuleParams`:

```typescript
export interface DefineModuleParams<IF extends Record<string, TailorAnyDBField>> {
  item?: CreateItemTypeParams<IF>;
  primitives: {
    db: { unit: PrimitivesDB["unit"] };
    commands: { convertQuantity: PrimitivesCommands["convertQuantity"] };
    queries: { convertAmount: PrimitivesQueries["convertAmount"] };
  };
}

export const defineModule = <const IF extends Record<string, TailorAnyDBField> = EmptyFields>(
  params: DefineModuleParams<IF>,
) => {
  const item = createItemType({
    ...params.item,
    unitType: params.primitives.db.unit,
  });

  return {
    db: { item },
    commands: {
      createItem: makeCreateItem({
        convertQuantity: params.primitives.commands.convertQuantity,
      }),
    },
  };
};

/** EmptyFields-instantiated alias — use instead of `ReturnType<typeof defineModule>`, which widens field generics to their constraint. */
export type ItemManagementModule = ReturnType<typeof defineModule<EmptyFields>>;
```

Re-export the alias from `index.ts` alongside `defineModule` (e.g. `export { defineModule, type ItemManagementModule } from "./module";`) so other modules can depend on this one without re-deriving `ReturnType<typeof defineModule>`.

## Permission Dependencies

Declare cross-module permission dependencies via `definePermissions`:

```typescript
export const { permissions, own, all } = definePermissions(
  "itemManagement",
  ["createItem", "updateItem", ...] as const,
  { deps: primitivesPermissions.all },
);
```

The `all` export merges own permissions with dependency permissions, so consumers get a complete permission set.
