diff --git a/CHANGELOG.md b/CHANGELOG.md index 45de969..da322af 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ Changelog +## [4.13.0](https://github.com/MikeDev75015/mongodb-dynamic-api/compare/v4.12.0...v4.13.0) (2026-05-26) + +### route-config + +* **route-config:** replace flat DynamicApiRouteConfig with discriminated union ([6046c61](https://github.com/MikeDev75015/mongodb-dynamic-api/commit/6046c6159eb43e68d9efec5bc5890fd9f4c1aadb)) + ## [4.12.0](https://github.com/MikeDev75015/mongodb-dynamic-api/compare/v4.11.0...v4.12.0) (2026-05-26) ### auth diff --git a/README/callbacks.md b/README/callbacks.md index 2df1b47..c29f51e 100644 --- a/README/callbacks.md +++ b/README/callbacks.md @@ -180,6 +180,8 @@ const onOrderCreatedAlt: AfterSaveCallback = async (order, methods, user) The `beforeSaveCallback` property is executed **before the database write**. Its purpose is to transform, enrich, or validate the data before it is persisted. The return value **replaces** the data that will be saved (except for delete callbacks which return `void`). +> **✅ No cast needed — discriminated union:** Since `DynamicApiRouteConfig` is a discriminated union, TypeScript narrows `beforeSaveCallback` to the exact context type for each `type` discriminant. You write `ctx.toCreate`, `ctx.update`, `ctx.replacement`, etc. with full autocompletion and no `as` cast. + ### Four callback signatures The signature varies depending on whether the route operates on a single document, multiple documents, or a delete operation. @@ -451,19 +453,25 @@ DynamicApiModule.forFeature({ ### Signature-to-route compatibility -| Route Type | Callback signature | Context type | `BodyDTO` supported | `entity` / `entities` | Returns | +`DynamicApiRouteConfig` is a **discriminated union** — TypeScript automatically narrows `beforeSaveCallback` to the exact callback type for each `type` discriminant, eliminating all manual casts in application code. + +| Route Type | Config type | Callback signature | Context type | `BodyDTO` supported | Returns | |---|---|---|---|---|---| -| `CreateOne` | `BeforeSaveCallback` | `BeforeSaveCreateContext` | ✅ | `undefined` | `Partial` | -| `CreateMany` | `BeforeSaveListCallback` | `BeforeSaveCreateManyContext` | ✅ | `undefined` | `Partial[]` | -| `UpdateOne` | `BeforeSaveCallback` | `BeforeSaveUpdateContext` | ✅ | Existing entity | `Partial` | -| `UpdateMany` | `BeforeSaveListCallback` | `BeforeSaveUpdateManyContext` | ✅ | Existing entities | `Partial[]` | -| `ReplaceOne` | `BeforeSaveCallback` | `BeforeSaveReplaceContext` | ✅ | Existing entity | `Partial` | -| `DuplicateOne` | `BeforeSaveCallback` | `BeforeSaveDuplicateContext` | ✅ | Existing entity | `Partial` | -| `DuplicateMany` | `BeforeSaveListCallback` | `BeforeSaveDuplicateManyContext` | ✅ | Existing entities | `Partial[]` | -| `DeleteOne` | `BeforeSaveDeleteCallback` | `BeforeSaveDeleteContext` | — | Entity to delete | `void` | -| `DeleteMany` | `BeforeSaveDeleteManyCallback` | `BeforeSaveDeleteManyContext` | — | Entities to delete | `void` | - -> **⚠️ Note:** `GetOne` and `GetMany` do **not** support `beforeSaveCallback` — they only support the `callback` (after save) property. +| `CreateOne` | `CreateOneRouteConfig` | `BeforeSaveCallback>` | `BeforeSaveCreateContext` | ✅ | `Partial` | +| `CreateMany` | `CreateManyRouteConfig` | `BeforeSaveListCallback>` | `BeforeSaveCreateManyContext` | ✅ | `Partial[]` | +| `UpdateOne` | `UpdateOneRouteConfig` | `BeforeSaveCallback>` | `BeforeSaveUpdateContext` | ✅ | `Partial` | +| `UpdateMany` | `UpdateManyRouteConfig` | `BeforeSaveListCallback>` | `BeforeSaveUpdateManyContext` | ✅ | `Partial[]` | +| `ReplaceOne` | `ReplaceOneRouteConfig` | `BeforeSaveCallback>` | `BeforeSaveReplaceContext` | ✅ | `Partial` | +| `DuplicateOne` | `DuplicateOneRouteConfig` | `BeforeSaveCallback>` | `BeforeSaveDuplicateContext` | ✅ | `Partial` | +| `DuplicateMany` | `DuplicateManyRouteConfig` | `BeforeSaveListCallback>` | `BeforeSaveDuplicateManyContext` | ✅ | `Partial[]` | +| `DeleteOne` | `DeleteOneRouteConfig` | `BeforeSaveDeleteCallback` | `BeforeSaveDeleteContext` | — | `void` | +| `DeleteMany` | `DeleteManyRouteConfig` | `BeforeSaveDeleteManyCallback` | `BeforeSaveDeleteManyContext` | — | `void` | + +> **⚠️ Note:** `GetOne`, `GetMany`, `Aggregate` and `Custom` routes do **not** have a `beforeSaveCallback` field — they only support the `callback` (after-save) property. + +> **⚠️ Breaking change:** `beforeDeleteCallback` and `cascade` are now exclusive to `DeleteOneRouteConfig` and `DeleteManyRouteConfig`. Placing them on any other route type is a compile-time error. + +> **`AnyBeforeSaveCallback`** is `@deprecated` — it is no longer needed in application code when using the discriminated union. It remains exported for backward compatibility with generic third-party helpers and will be removed in v5. --- diff --git a/README/route-config.md b/README/route-config.md index 15e387e..eddc92f 100644 --- a/README/route-config.md +++ b/README/route-config.md @@ -460,20 +460,31 @@ Callbacks let you hook into the lifecycle of a service operation, either **befor ### beforeSaveCallback -Four signatures depending on the route type: - -| Callback type | Used by | Context type | Returns | -|---|---|---|---| -| `BeforeSaveCallback` | `CreateOne`, `UpdateOne`, `ReplaceOne`, `DuplicateOne` | `BeforeSave*Context` | `Partial` | -| `BeforeSaveListCallback` | `CreateMany`, `UpdateMany`, `DuplicateMany` | `BeforeSave*Context` | `Partial[]` | -| `BeforeSaveDeleteCallback` | `DeleteOne` | `BeforeSaveDeleteContext` | `void` | -| `BeforeSaveDeleteManyCallback` | `DeleteMany` | `BeforeSaveDeleteManyContext` | `void` | +`DynamicApiRouteConfig` is a **discriminated union** — TypeScript narrows `beforeSaveCallback` to the exact context type for each `type` discriminant. No cast is ever needed in application code. + +| Route type | Config type | Callback type | Context type | Returns | +|---|---|---|---|---| +| `CreateOne` | `CreateOneRouteConfig` | `BeforeSaveCallback>` | `BeforeSaveCreateContext` | `Partial` | +| `CreateMany` | `CreateManyRouteConfig` | `BeforeSaveListCallback>` | `BeforeSaveCreateManyContext` | `Partial[]` | +| `UpdateOne` | `UpdateOneRouteConfig` | `BeforeSaveCallback>` | `BeforeSaveUpdateContext` | `Partial` | +| `UpdateMany` | `UpdateManyRouteConfig` | `BeforeSaveListCallback>` | `BeforeSaveUpdateManyContext` | `Partial[]` | +| `ReplaceOne` | `ReplaceOneRouteConfig` | `BeforeSaveCallback>` | `BeforeSaveReplaceContext` | `Partial` | +| `DuplicateOne` | `DuplicateOneRouteConfig` | `BeforeSaveCallback>` | `BeforeSaveDuplicateContext` | `Partial` | +| `DuplicateMany` | `DuplicateManyRouteConfig` | `BeforeSaveListCallback>` | `BeforeSaveDuplicateManyContext` | `Partial[]` | +| `DeleteOne` | `DeleteOneRouteConfig` | `BeforeSaveDeleteCallback` | `BeforeSaveDeleteContext` | `void` | +| `DeleteMany` | `DeleteManyRouteConfig` | `BeforeSaveDeleteManyCallback` | `BeforeSaveDeleteManyContext` | `void` | +| `GetOne` | `GetOneRouteConfig` | — *(no beforeSaveCallback)* | — | — | +| `GetMany` | `GetManyRouteConfig` | — *(no beforeSaveCallback)* | — | — | +| `Aggregate` | `AggregateRouteConfig` | — *(no beforeSaveCallback)* | — | — | +| `Custom` | `CustomOperationRouteConfig` | — *(no beforeSaveCallback)* | — | — | + +> **`beforeDeleteCallback` and `cascade`** are only available on `DeleteOneRouteConfig` and `DeleteManyRouteConfig`. Each route provides a typed context (`BeforeSaveCreateContext`, `BeforeSaveUpdateContext`, etc.) and the authenticated `user` as the last parameter. The `User` generic defaults to `unknown` — pass your user entity type for full type safety (see [Callbacks guide](https://github.com/MikeDev75015/mongodb-dynamic-api/blob/main/README/callbacks.md#typing-the-user-parameter)). Context types accept an optional **`BodyDTO` generic** (defaults to `Entity`). Pass your custom body DTO class when using `dTOs.body` to get full type safety on the body fields (see [Custom Body DTO](https://github.com/MikeDev75015/mongodb-dynamic-api/blob/main/README/callbacks.md#custom-body-dto--bodydto-generic)). -**Example — Hash a password before saving (typed context):** +**Example — Hash a password before saving (typed context, no cast):** ```typescript import * as bcrypt from 'bcrypt'; @@ -485,14 +496,13 @@ class CreateUserDto { displayName?: string; } -// ✅ BodyDTO generic — ctx.toCreate is Partial, no cast needed -const beforeCreate: BeforeSaveCallback> = +// ✅ ctx.toCreate is Partial — TS narrows automatically via discriminant type: 'CreateOne' +const beforeCreate: BeforeSaveCallback> = async (_entity, ctx, _methods, _user) => { - const { toCreate } = ctx; - if (toCreate.password) { - toCreate.password = await bcrypt.hash(toCreate.password, 10); + if (ctx.toCreate.password) { + ctx.toCreate.password = await bcrypt.hash(ctx.toCreate.password, 10); } - return toCreate; + return ctx.toCreate; }; DynamicApiModule.forFeature({ diff --git a/libs/dynamic-api/src/interfaces/dynamic-api-route-config.interface.spec.ts b/libs/dynamic-api/src/interfaces/dynamic-api-route-config.interface.spec.ts new file mode 100644 index 0000000..63bac1e --- /dev/null +++ b/libs/dynamic-api/src/interfaces/dynamic-api-route-config.interface.spec.ts @@ -0,0 +1,458 @@ +/** + * Compile-time + runtime tests for the discriminated union `DynamicApiRouteConfig`. + * + * Strategy: + * - Runtime checks verify that each per-route config type is structurally valid. + * - `@ts-expect-error` lines verify that TS rejects wrong `beforeSaveCallback` + * signatures on the wrong route type, proving contravariance is solved. + */ +import { CallbackMethods } from './dynamic-api-service-callback.interface'; +import { + AggregateRouteConfig, + BaseRouteConfig, + CreateManyRouteConfig, + CreateOneRouteConfig, + DeleteManyRouteConfig, + DeleteOneRouteConfig, + DuplicateManyRouteConfig, + DuplicateOneRouteConfig, + DynamicApiRouteConfig, + DynamicAPIRouteConfig, + GetManyRouteConfig, + GetOneRouteConfig, + ReplaceOneRouteConfig, + UpdateManyRouteConfig, + UpdateOneRouteConfig, + CustomOperationRouteConfig, +} from './dynamic-api-route-config.interface'; +import { + BeforeSaveCallback, + BeforeSaveCreateContext, + BeforeSaveCreateManyContext, + BeforeSaveDeleteCallback, + BeforeSaveDeleteContext, + BeforeSaveDeleteManyCallback, + BeforeSaveDeleteManyContext, + BeforeSaveDuplicateContext, + BeforeSaveDuplicateManyContext, + BeforeSaveListCallback, + BeforeSaveReplaceContext, + BeforeSaveUpdateContext, + BeforeSaveUpdateManyContext, +} from './dynamic-api-service-before-save-callback.interface'; +import { BaseEntity } from '../models'; + +// --------------------------------------------------------------------------- +// Domain fixtures +// --------------------------------------------------------------------------- + +class Item extends BaseEntity { + name: string; + price: number; +} + +class CreateItemDto { + name: string; + price: number; + sku?: string; +} + +const fakeCallbackMethods = {} as CallbackMethods; + +// --------------------------------------------------------------------------- +// BaseRouteConfig — common fields present on every variant +// --------------------------------------------------------------------------- + +describe('BaseRouteConfig', () => { + it('should allow all common optional fields', () => { + const base: BaseRouteConfig = { + isPublic: true, + disableCache: false, + description: 'test', + version: '1', + subPath: 'sub', + validationPipeOptions: { transform: true }, + }; + + expect(base.isPublic).toBe(true); + }); +}); + +// --------------------------------------------------------------------------- +// Per-route configs — structural validity +// --------------------------------------------------------------------------- + +describe('per-route config structural correctness', () => { + it.each<[string, DynamicApiRouteConfig]>([ + ['CreateOne', { type: 'CreateOne' }], + ['CreateMany', { type: 'CreateMany' }], + ['UpdateOne', { type: 'UpdateOne' }], + ['UpdateMany', { type: 'UpdateMany' }], + ['ReplaceOne', { type: 'ReplaceOne' }], + ['DuplicateOne', { type: 'DuplicateOne' }], + ['DuplicateMany', { type: 'DuplicateMany' }], + ['DeleteOne', { type: 'DeleteOne' }], + ['DeleteMany', { type: 'DeleteMany' }], + ['GetOne', { type: 'GetOne' }], + ['GetMany', { type: 'GetMany' }], + ['Aggregate', { type: 'Aggregate' }], + ['Custom', { type: 'Custom' }], + ])('%s — minimal config (no beforeSaveCallback) is valid', (_label, cfg) => { + expect(cfg.type).toBeTruthy(); + }); + + it('DeleteOne accepts cascade + beforeDeleteCallback', () => { + const cfg: DeleteOneRouteConfig = { + type: 'DeleteOne', + cascade: [{ entity: class Ref extends BaseEntity {}, foreignKey: 'itemId', on: 'delete' }], + beforeDeleteCallback: async () => undefined, + }; + + expect(cfg.cascade).toHaveLength(1); + expect(cfg.beforeDeleteCallback).toBeDefined(); + }); + + it('DeleteMany accepts cascade + beforeDeleteCallback', () => { + const cfg: DeleteManyRouteConfig = { + type: 'DeleteMany', + cascade: [{ entity: class Ref extends BaseEntity {}, foreignKey: 'itemId', on: 'delete' }], + beforeDeleteCallback: async () => undefined, + }; + + expect(cfg.cascade).toHaveLength(1); + expect(cfg.beforeDeleteCallback).toBeDefined(); + }); +}); + +// --------------------------------------------------------------------------- +// CreateOne — typed beforeSaveCallback (no cast needed) +// --------------------------------------------------------------------------- + +describe('CreateOneRouteConfig — beforeSaveCallback type', () => { + it('accepts a correctly-typed BeforeSaveCallback with BeforeSaveCreateContext', async () => { + const cb: BeforeSaveCallback> = + async (_entity, ctx, _methods) => ({ ...ctx.toCreate, name: ctx.toCreate.name ?? 'default' }); + + const cfg: CreateOneRouteConfig = { + type: 'CreateOne', + beforeSaveCallback: cb, + }; + + const result = await cfg.beforeSaveCallback!( + undefined, + { toCreate: { name: 'widget', price: 9.99 } }, + fakeCallbackMethods, + ); + + expect(result.name).toBe('widget'); + }); + + it('accepts a typed callback with CreateItemDto BodyDTO inline — ctx.toCreate typed correctly', async () => { + // Use explicit BodyDTO generic on the callback type; ctx.toCreate is Partial + type CreateItemCb = BeforeSaveCallback>; + + const cb: CreateItemCb = async (_entity, ctx) => ({ + name: ctx.toCreate.name ?? '', + price: ctx.toCreate.price ?? 0, + // ctx.toCreate.sku is accessible without cast because BodyDTO = CreateItemDto + }); + + // Inline callback; ctx shape matches BeforeSaveCreateContext + const cfgInline: CreateOneRouteConfig = { + type: 'CreateOne', + beforeSaveCallback: async (_entity, ctx) => ({ name: ctx.toCreate.name, price: ctx.toCreate.price }), + }; + + const result = await cfgInline.beforeSaveCallback!( + undefined, + { toCreate: { name: 'gadget', price: 5 } }, + fakeCallbackMethods, + ); + + expect(result.name).toBe('gadget'); + + // Verify cb independently (typed with BodyDTO) + const ctxWithDto: BeforeSaveCreateContext = { toCreate: { name: 'gadget', price: 5, sku: 'SKU-001' } }; + const dtoResult = await cb(undefined, ctxWithDto, fakeCallbackMethods); + expect(dtoResult.name).toBe('gadget'); + }); +}); + +// --------------------------------------------------------------------------- +// CreateMany — typed beforeSaveCallback +// --------------------------------------------------------------------------- + +describe('CreateManyRouteConfig — beforeSaveCallback type', () => { + it('accepts BeforeSaveListCallback with BeforeSaveCreateManyContext', async () => { + const cb: BeforeSaveListCallback> = + async (_entities, ctx) => ctx.toCreate.map((i) => ({ name: i.name ?? '' })); + + const cfg: CreateManyRouteConfig = { + type: 'CreateMany', + beforeSaveCallback: cb, + }; + + const result = await cfg.beforeSaveCallback!( + undefined, + { toCreate: [{ name: 'a' }, { name: 'b' }] }, + fakeCallbackMethods, + ); + + expect(result).toHaveLength(2); + expect(result[0].name).toBe('a'); + }); +}); + +// --------------------------------------------------------------------------- +// UpdateOne — typed beforeSaveCallback +// --------------------------------------------------------------------------- + +describe('UpdateOneRouteConfig — beforeSaveCallback type', () => { + it('accepts BeforeSaveCallback with BeforeSaveUpdateContext', async () => { + const cb: BeforeSaveCallback> = + async (_entity, ctx) => ({ ...ctx.update }); + + const cfg: UpdateOneRouteConfig = { + type: 'UpdateOne', + beforeSaveCallback: cb, + }; + + const result = await cfg.beforeSaveCallback!( + undefined, + { id: 'item-1', update: { price: 12 } }, + fakeCallbackMethods, + ); + + expect(result.price).toBe(12); + }); +}); + +// --------------------------------------------------------------------------- +// UpdateMany — typed beforeSaveCallback +// --------------------------------------------------------------------------- + +describe('UpdateManyRouteConfig — beforeSaveCallback type', () => { + it('accepts BeforeSaveListCallback with BeforeSaveUpdateManyContext', async () => { + const cb: BeforeSaveListCallback> = + async (_entities, ctx) => ctx.ids.map(() => ({ price: ctx.update.price ?? 0 })); + + const cfg: UpdateManyRouteConfig = { + type: 'UpdateMany', + beforeSaveCallback: cb, + }; + + const result = await cfg.beforeSaveCallback!( + undefined, + { ids: ['a', 'b'], update: { price: 5 } }, + fakeCallbackMethods, + ); + + expect(result).toHaveLength(2); + }); +}); + +// --------------------------------------------------------------------------- +// ReplaceOne — typed beforeSaveCallback +// --------------------------------------------------------------------------- + +describe('ReplaceOneRouteConfig — beforeSaveCallback type', () => { + it('accepts BeforeSaveCallback with BeforeSaveReplaceContext', async () => { + const cb: BeforeSaveCallback> = + async (_entity, ctx) => ({ ...ctx.replacement }); + + const cfg: ReplaceOneRouteConfig = { + type: 'ReplaceOne', + beforeSaveCallback: cb, + }; + + const result = await cfg.beforeSaveCallback!( + undefined, + { id: 'x', replacement: { name: 'replaced', price: 0 } }, + fakeCallbackMethods, + ); + + expect(result.name).toBe('replaced'); + }); +}); + +// --------------------------------------------------------------------------- +// DuplicateOne — typed beforeSaveCallback +// --------------------------------------------------------------------------- + +describe('DuplicateOneRouteConfig — beforeSaveCallback type', () => { + it('accepts BeforeSaveCallback with BeforeSaveDuplicateContext', async () => { + const cb: BeforeSaveCallback> = + async (_entity, ctx) => ({ ...(ctx.override ?? {}) }); + + const cfg: DuplicateOneRouteConfig = { + type: 'DuplicateOne', + beforeSaveCallback: cb, + }; + + const result = await cfg.beforeSaveCallback!( + undefined, + { id: 'x', override: { name: 'clone' } }, + fakeCallbackMethods, + ); + + expect(result.name).toBe('clone'); + }); +}); + +// --------------------------------------------------------------------------- +// DuplicateMany — typed beforeSaveCallback +// --------------------------------------------------------------------------- + +describe('DuplicateManyRouteConfig — beforeSaveCallback type', () => { + it('accepts BeforeSaveListCallback with BeforeSaveDuplicateManyContext', async () => { + const cb: BeforeSaveListCallback> = + async (_entities, ctx) => ctx.ids.map(() => ({ ...(ctx.override ?? {}) })); + + const cfg: DuplicateManyRouteConfig = { + type: 'DuplicateMany', + beforeSaveCallback: cb, + }; + + const result = await cfg.beforeSaveCallback!( + undefined, + { ids: ['a', 'b'] }, + fakeCallbackMethods, + ); + + expect(result).toHaveLength(2); + }); +}); + +// --------------------------------------------------------------------------- +// DeleteOne — typed beforeSaveCallback +// --------------------------------------------------------------------------- + +describe('DeleteOneRouteConfig — beforeSaveCallback type', () => { + it('accepts BeforeSaveDeleteCallback with BeforeSaveDeleteContext', async () => { + const called: string[] = []; + + const cb: BeforeSaveDeleteCallback = + async (_entity, ctx) => { called.push(ctx.id); }; + + const cfg: DeleteOneRouteConfig = { + type: 'DeleteOne', + beforeSaveCallback: cb, + }; + + await cfg.beforeSaveCallback!(undefined, { id: 'item-42' }, fakeCallbackMethods); + + expect(called).toEqual(['item-42']); + }); +}); + +// --------------------------------------------------------------------------- +// DeleteMany — typed beforeSaveCallback +// --------------------------------------------------------------------------- + +describe('DeleteManyRouteConfig — beforeSaveCallback type', () => { + it('accepts BeforeSaveDeleteManyCallback with BeforeSaveDeleteManyContext', async () => { + const collected: string[] = []; + + const cb: BeforeSaveDeleteManyCallback = + async (_entities, ctx) => { collected.push(...ctx.ids); }; + + const cfg: DeleteManyRouteConfig = { + type: 'DeleteMany', + beforeSaveCallback: cb, + }; + + await cfg.beforeSaveCallback!([], { ids: ['a', 'b', 'c'] }, fakeCallbackMethods); + + expect(collected).toEqual(['a', 'b', 'c']); + }); +}); + +// --------------------------------------------------------------------------- +// GetOne / GetMany / Aggregate / Custom — no beforeSaveCallback field +// --------------------------------------------------------------------------- + +describe('read-only and utility routes — no beforeSaveCallback', () => { + it('GetOneRouteConfig does not have beforeSaveCallback property', () => { + const cfg: GetOneRouteConfig = { type: 'GetOne' }; + + // @ts-expect-error — beforeSaveCallback is not declared on GetOneRouteConfig + expect(cfg.beforeSaveCallback).toBeUndefined(); + }); + + it('GetManyRouteConfig does not have beforeSaveCallback property', () => { + const cfg: GetManyRouteConfig = { type: 'GetMany' }; + + // @ts-expect-error — beforeSaveCallback is not declared on GetManyRouteConfig + expect(cfg.beforeSaveCallback).toBeUndefined(); + }); + + it('AggregateRouteConfig does not have beforeSaveCallback property', () => { + const cfg: AggregateRouteConfig = { type: 'Aggregate' }; + + // @ts-expect-error — beforeSaveCallback is not declared on AggregateRouteConfig + expect(cfg.beforeSaveCallback).toBeUndefined(); + }); + + it('CustomOperationRouteConfig does not have beforeSaveCallback property', () => { + const cfg: CustomOperationRouteConfig = { type: 'Custom' }; + + // @ts-expect-error — beforeSaveCallback is not declared on CustomOperationRouteConfig + expect(cfg.beforeSaveCallback).toBeUndefined(); + }); +}); + +// --------------------------------------------------------------------------- +// Discriminated union narrowing — DynamicApiRouteConfig +// --------------------------------------------------------------------------- + +describe('DynamicApiRouteConfig — discriminated union narrowing', () => { + it('narrows to CreateOneRouteConfig when type === CreateOne', () => { + const cfg: DynamicApiRouteConfig = { + type: 'CreateOne', + beforeSaveCallback: async (_entity, ctx) => ({ name: ctx.toCreate.name }), + }; + + if (cfg.type === 'CreateOne') { + // cfg.beforeSaveCallback is narrowed to BeforeSaveCallback> + expect(cfg.beforeSaveCallback).toBeDefined(); + } + }); + + it('narrows to DeleteOneRouteConfig when type === DeleteOne', () => { + const cfg: DynamicApiRouteConfig = { + type: 'DeleteOne', + cascade: [], + }; + + if (cfg.type === 'DeleteOne') { + expect(cfg.cascade).toEqual([]); + } + }); + + it('union accepts an array of heterogeneous route configs', () => { + const routes: DynamicApiRouteConfig[] = [ + { type: 'CreateOne', beforeSaveCallback: async (_e, ctx) => ({ ...ctx.toCreate }) }, + { type: 'UpdateOne', beforeSaveCallback: async (_e, ctx) => ({ ...ctx.update }) }, + { type: 'GetMany' }, + { type: 'DeleteOne', beforeSaveCallback: async () => undefined }, + ]; + + expect(routes).toHaveLength(4); + }); +}); + +// --------------------------------------------------------------------------- +// Deprecated alias — DynamicAPIRouteConfig still works +// --------------------------------------------------------------------------- + +describe('DynamicAPIRouteConfig (deprecated alias)', () => { + it('is assignable from any per-route config', () => { + const cfg: DynamicAPIRouteConfig = { type: 'CreateOne' }; + + expect(cfg.type).toBe('CreateOne'); + }); +}); + + + + diff --git a/libs/dynamic-api/src/interfaces/dynamic-api-route-config.interface.ts b/libs/dynamic-api/src/interfaces/dynamic-api-route-config.interface.ts index cb4a126..0ea238d 100644 --- a/libs/dynamic-api/src/interfaces/dynamic-api-route-config.interface.ts +++ b/libs/dynamic-api/src/interfaces/dynamic-api-route-config.interface.ts @@ -4,8 +4,23 @@ import { AbilityPredicate, PredicateBehavior } from './dynamic-api-ability.inter import { BroadcastConfig } from './dynamic-api-broadcast-config.interface'; import { CascadeConfig } from './dynamic-api-cascade-config.interface'; import { DTOsBundle } from './dynamic-api-route-dtos-bundle.type'; -import { RouteType } from './dynamic-api-route-type.type'; -import { AnyBeforeDeleteCallback, AnyBeforeSaveCallback } from './dynamic-api-service-before-save-callback.interface'; +import { + BeforeDeleteCallback, + BeforeDeleteManyCallback, + BeforeSaveCallback, + BeforeSaveCreateContext, + BeforeSaveCreateManyContext, + BeforeSaveDeleteCallback, + BeforeSaveDeleteContext, + BeforeSaveDeleteManyCallback, + BeforeSaveDeleteManyContext, + BeforeSaveDuplicateContext, + BeforeSaveDuplicateManyContext, + BeforeSaveListCallback, + BeforeSaveReplaceContext, + BeforeSaveUpdateContext, + BeforeSaveUpdateManyContext, +} from './dynamic-api-service-before-save-callback.interface'; import { AfterSaveCallback } from './dynamic-api-service-callback.interface'; import { DynamicApiWebSocketOptions } from './dynamic-api-web-socket.interface'; @@ -23,8 +38,11 @@ import { DynamicApiWebSocketOptions } from './dynamic-api-web-socket.interface'; */ type FromUserMap = Partial unknown)>>; -interface DynamicApiRouteConfig { - type: RouteType; +/** + * Common fields shared by every route configuration. + * Do not use directly — extend or pick from the specific per-route config type. + */ +interface BaseRouteConfig { isPublic?: boolean; disableCache?: boolean; description?: string; @@ -34,14 +52,68 @@ interface DynamicApiRouteConfig { validationPipeOptions?: ValidationPipeOptions; abilityPredicate?: AbilityPredicate; predicateBehavior?: PredicateBehavior; - beforeSaveCallback?: AnyBeforeSaveCallback; + callback?: AfterSaveCallback; + webSocket?: DynamicApiWebSocketOptions; + eventName?: string; + broadcast?: BroadcastConfig; + isArrayResponse?: boolean; + useInterceptors?: Type[]; + fromUser?: FromUserMap; +} + +/** Route config for `CreateOne` — `beforeSaveCallback` receives {@link BeforeSaveCreateContext}. */ +interface CreateOneRouteConfig extends BaseRouteConfig { + type: 'CreateOne'; + beforeSaveCallback?: BeforeSaveCallback>; +} + +/** Route config for `CreateMany` — `beforeSaveCallback` receives {@link BeforeSaveCreateManyContext}. */ +interface CreateManyRouteConfig extends BaseRouteConfig { + type: 'CreateMany'; + beforeSaveCallback?: BeforeSaveListCallback>; +} + +/** Route config for `UpdateOne` — `beforeSaveCallback` receives {@link BeforeSaveUpdateContext}. */ +interface UpdateOneRouteConfig extends BaseRouteConfig { + type: 'UpdateOne'; + beforeSaveCallback?: BeforeSaveCallback>; +} + +/** Route config for `UpdateMany` — `beforeSaveCallback` receives {@link BeforeSaveUpdateManyContext}. */ +interface UpdateManyRouteConfig extends BaseRouteConfig { + type: 'UpdateMany'; + beforeSaveCallback?: BeforeSaveListCallback>; +} + +/** Route config for `ReplaceOne` — `beforeSaveCallback` receives {@link BeforeSaveReplaceContext}. */ +interface ReplaceOneRouteConfig extends BaseRouteConfig { + type: 'ReplaceOne'; + beforeSaveCallback?: BeforeSaveCallback>; +} + +/** Route config for `DuplicateOne` — `beforeSaveCallback` receives {@link BeforeSaveDuplicateContext}. */ +interface DuplicateOneRouteConfig extends BaseRouteConfig { + type: 'DuplicateOne'; + beforeSaveCallback?: BeforeSaveCallback>; +} + +/** Route config for `DuplicateMany` — `beforeSaveCallback` receives {@link BeforeSaveDuplicateManyContext}. */ +interface DuplicateManyRouteConfig extends BaseRouteConfig { + type: 'DuplicateMany'; + beforeSaveCallback?: BeforeSaveListCallback>; +} + +/** Route config for `DeleteOne` — `beforeSaveCallback` receives {@link BeforeSaveDeleteContext}. */ +interface DeleteOneRouteConfig extends BaseRouteConfig { + type: 'DeleteOne'; + beforeSaveCallback?: BeforeSaveDeleteCallback; /** * Pre-delete hook executed **before** the MongoDB delete operation and **outside** * the internal error-catch block. Any exception thrown (e.g. `ForbiddenException`, * `BadRequestException`) propagates as a proper HTTP error to the client and - * **aborts** the delete. Compatible with `DeleteOne` and `DeleteMany` routes only. + * **aborts** the delete. */ - beforeDeleteCallback?: AnyBeforeDeleteCallback; + beforeDeleteCallback?: BeforeDeleteCallback; /** * Cascade delete configuration. After a successful parent delete, each entry * triggers deletion of child documents that reference the parent via `foreignKey`. @@ -49,18 +121,96 @@ interface DynamicApiRouteConfig { * `'softDelete'` for soft-delete) are executed. */ cascade?: CascadeConfig[]; - callback?: AfterSaveCallback; - webSocket?: DynamicApiWebSocketOptions; - eventName?: string; - broadcast?: BroadcastConfig; - isArrayResponse?: boolean; - useInterceptors?: Type[]; - fromUser?: FromUserMap; } +/** Route config for `DeleteMany` — `beforeSaveCallback` receives {@link BeforeSaveDeleteManyContext}. */ +interface DeleteManyRouteConfig extends BaseRouteConfig { + type: 'DeleteMany'; + beforeSaveCallback?: BeforeSaveDeleteManyCallback; + /** + * Pre-delete hook executed **before** the MongoDB delete operation and **outside** + * the internal error-catch block. Any exception thrown (e.g. `ForbiddenException`, + * `BadRequestException`) propagates as a proper HTTP error to the client and + * **aborts** the delete. + */ + beforeDeleteCallback?: BeforeDeleteManyCallback; + /** + * Cascade delete configuration. After a successful parent delete, each entry + * triggers deletion of child documents that reference the parent via `foreignKey`. + * Only entries whose `on` value matches the delete mode (`'delete'` for hard-delete, + * `'softDelete'` for soft-delete) are executed. + */ + cascade?: CascadeConfig[]; +} + +/** Route config for `GetOne` — no `beforeSaveCallback`. */ +interface GetOneRouteConfig extends BaseRouteConfig { + type: 'GetOne'; +} + +/** Route config for `GetMany` — no `beforeSaveCallback`. */ +interface GetManyRouteConfig extends BaseRouteConfig { + type: 'GetMany'; +} + +/** Route config for `Aggregate` — no `beforeSaveCallback`. */ +interface AggregateRouteConfig extends BaseRouteConfig { + type: 'Aggregate'; +} + +/** Route config for `Custom` operation routes — no `beforeSaveCallback`. */ +interface CustomOperationRouteConfig extends BaseRouteConfig { + type: 'Custom'; +} + +/** + * Discriminated union of all per-route configuration types. + * TypeScript narrows `beforeSaveCallback` to the exact context type for each `type` discriminant, + * eliminating the need for manual casts when writing typed callbacks. + * + * @example + * // CreateOne — ctx is BeforeSaveCreateContext, no cast needed + * const cfg: DynamicApiRouteConfig = { + * type: 'CreateOne', + * beforeSaveCallback: async (_entity, ctx, _methods) => ({ ...ctx.toCreate }), + * }; + */ +type DynamicApiRouteConfig = + | CreateOneRouteConfig + | CreateManyRouteConfig + | UpdateOneRouteConfig + | UpdateManyRouteConfig + | ReplaceOneRouteConfig + | DuplicateOneRouteConfig + | DuplicateManyRouteConfig + | DeleteOneRouteConfig + | DeleteManyRouteConfig + | GetOneRouteConfig + | GetManyRouteConfig + | AggregateRouteConfig + | CustomOperationRouteConfig; + /** * @deprecated Use `DynamicApiRouteConfig` instead. Will be removed in v5. */ type DynamicAPIRouteConfig = DynamicApiRouteConfig; -export { DynamicApiRouteConfig, DynamicAPIRouteConfig, FromUserMap }; +export { + BaseRouteConfig, + CreateOneRouteConfig, + CreateManyRouteConfig, + UpdateOneRouteConfig, + UpdateManyRouteConfig, + ReplaceOneRouteConfig, + DuplicateOneRouteConfig, + DuplicateManyRouteConfig, + DeleteOneRouteConfig, + DeleteManyRouteConfig, + GetOneRouteConfig, + GetManyRouteConfig, + AggregateRouteConfig, + CustomOperationRouteConfig, + DynamicApiRouteConfig, + DynamicAPIRouteConfig, + FromUserMap, +}; diff --git a/libs/dynamic-api/src/interfaces/dynamic-api-service-before-save-callback.interface.ts b/libs/dynamic-api/src/interfaces/dynamic-api-service-before-save-callback.interface.ts index ce16e46..0c1c3cf 100644 --- a/libs/dynamic-api/src/interfaces/dynamic-api-service-before-save-callback.interface.ts +++ b/libs/dynamic-api/src/interfaces/dynamic-api-service-before-save-callback.interface.ts @@ -121,6 +121,13 @@ type BeforeSaveDeleteManyCallback Promise; +/** + * @deprecated + * `AnyBeforeSaveCallback` is no longer needed when using `DynamicApiRouteConfig` (discriminated union). + * Each per-route config type (`CreateOneRouteConfig`, `UpdateOneRouteConfig`, …) already carries a + * precisely-typed `beforeSaveCallback` — no cast or wide union type is required in application code. + * This type is kept for backwards compatibility with generic helpers and will be removed in v5. + */ type AnyBeforeSaveCallback = | BeforeSaveCallback | BeforeSaveListCallback diff --git a/libs/dynamic-api/src/routes/create-many/create-many.module.ts b/libs/dynamic-api/src/routes/create-many/create-many.module.ts index 8dfc504..f13d8bd 100644 --- a/libs/dynamic-api/src/routes/create-many/create-many.module.ts +++ b/libs/dynamic-api/src/routes/create-many/create-many.module.ts @@ -2,7 +2,7 @@ import { DynamicModule, Module, ModuleMetadata, Type, ValidationPipeOptions } fr import { GatewayMetadata } from '@nestjs/websockets'; import { DynamicApiModule } from '../../dynamic-api.module'; import { getDisplayedName, initializeConfigFromOptions } from '../../helpers'; -import { BeforeSaveListCallback, DynamicApiControllerOptions, DynamicAPIRouteConfig, DynamicApiWebSocketOptions } from '../../interfaces'; +import { CreateManyRouteConfig, DynamicApiControllerOptions, DynamicApiWebSocketOptions } from '../../interfaces'; import { BaseEntity } from '../../models'; import { DynamicApiBroadcastService } from '../../services'; import { @@ -18,7 +18,7 @@ export class CreateManyModule { databaseModule: DynamicModule, entity: Type, controllerOptions: DynamicApiControllerOptions, - routeConfig: DynamicAPIRouteConfig, + routeConfig: CreateManyRouteConfig, version?: string, validationPipeOptions?: ValidationPipeOptions, webSocket?: DynamicApiWebSocketOptions, @@ -36,7 +36,7 @@ export class CreateManyModule { version, validationPipeOptions, ); - const ServiceProvider = createCreateManyServiceProvider(entity, displayedName, version, routeConfig.callback, routeConfig.beforeSaveCallback as BeforeSaveListCallback); + const ServiceProvider = createCreateManyServiceProvider(entity, displayedName, version, routeConfig.callback, routeConfig.beforeSaveCallback); const hasBroadcast = !!routeConfig.broadcast; const gatewayOptions = webSocket diff --git a/libs/dynamic-api/src/routes/create-one/create-one.module.ts b/libs/dynamic-api/src/routes/create-one/create-one.module.ts index 9d5ba44..7b330bb 100644 --- a/libs/dynamic-api/src/routes/create-one/create-one.module.ts +++ b/libs/dynamic-api/src/routes/create-one/create-one.module.ts @@ -2,7 +2,7 @@ import { DynamicModule, Module, ModuleMetadata, Type, ValidationPipeOptions } fr import { GatewayMetadata } from '@nestjs/websockets'; import { DynamicApiModule } from '../../dynamic-api.module'; import { getDisplayedName, initializeConfigFromOptions } from '../../helpers'; -import { BeforeSaveCallback, DynamicApiControllerOptions, DynamicAPIRouteConfig, DynamicApiWebSocketOptions } from '../../interfaces'; +import { CreateOneRouteConfig, DynamicApiControllerOptions, DynamicApiWebSocketOptions } from '../../interfaces'; import { BaseEntity } from '../../models'; import { DynamicApiBroadcastService } from '../../services'; import { createCreateOneController, createCreateOneGateway, createCreateOneServiceProvider } from './create-one.helper'; @@ -14,7 +14,7 @@ export class CreateOneModule { databaseModule: DynamicModule, entity: Type, controllerOptions: DynamicApiControllerOptions, - routeConfig: DynamicAPIRouteConfig, + routeConfig: CreateOneRouteConfig, version?: string, validationPipeOptions?: ValidationPipeOptions, webSocket?: DynamicApiWebSocketOptions, @@ -33,7 +33,7 @@ export class CreateOneModule { validationPipeOptions, ); const ServiceProvider = createCreateOneServiceProvider( - entity, displayedName, version, routeConfig.callback, routeConfig.beforeSaveCallback as BeforeSaveCallback, + entity, displayedName, version, routeConfig.callback, routeConfig.beforeSaveCallback, ); const hasBroadcast = !!routeConfig.broadcast; diff --git a/libs/dynamic-api/src/routes/delete-many/delete-many.module.ts b/libs/dynamic-api/src/routes/delete-many/delete-many.module.ts index 5059ecf..5007c73 100644 --- a/libs/dynamic-api/src/routes/delete-many/delete-many.module.ts +++ b/libs/dynamic-api/src/routes/delete-many/delete-many.module.ts @@ -2,7 +2,7 @@ import { DynamicModule, Module, ModuleMetadata, Type, ValidationPipeOptions } fr import { GatewayMetadata } from '@nestjs/websockets'; import { DynamicApiModule } from '../../dynamic-api.module'; import { getDisplayedName, initializeConfigFromOptions } from '../../helpers'; -import { BeforeSaveDeleteManyCallback, BeforeDeleteManyCallback, BeforeSaveDeleteManyContext, DynamicApiControllerOptions, DynamicAPIRouteConfig, DynamicApiWebSocketOptions } from '../../interfaces'; +import { DeleteManyRouteConfig, DynamicApiControllerOptions, DynamicApiWebSocketOptions } from '../../interfaces'; import { BaseEntity } from '../../models'; import { DynamicApiBroadcastService } from '../../services'; import { @@ -18,7 +18,7 @@ export class DeleteManyModule { databaseModule: DynamicModule, entity: Type, controllerOptions: DynamicApiControllerOptions, - routeConfig: DynamicAPIRouteConfig, + routeConfig: DeleteManyRouteConfig, version?: string, validationPipeOptions?: ValidationPipeOptions, webSocket?: DynamicApiWebSocketOptions, @@ -41,8 +41,8 @@ export class DeleteManyModule { displayedName, version, routeConfig.callback, - routeConfig.beforeSaveCallback as BeforeSaveDeleteManyCallback, - routeConfig.beforeDeleteCallback as BeforeDeleteManyCallback, + routeConfig.beforeSaveCallback, + routeConfig.beforeDeleteCallback, routeConfig.cascade, ); diff --git a/libs/dynamic-api/src/routes/delete-one/delete-one.module.ts b/libs/dynamic-api/src/routes/delete-one/delete-one.module.ts index cb4e277..5aa7b84 100644 --- a/libs/dynamic-api/src/routes/delete-one/delete-one.module.ts +++ b/libs/dynamic-api/src/routes/delete-one/delete-one.module.ts @@ -2,7 +2,7 @@ import { DynamicModule, Module, ModuleMetadata, Type, ValidationPipeOptions } fr import { GatewayMetadata } from '@nestjs/websockets'; import { DynamicApiModule } from '../../dynamic-api.module'; import { getDisplayedName, initializeConfigFromOptions } from '../../helpers'; -import { BeforeSaveDeleteCallback, BeforeDeleteCallback, BeforeSaveDeleteContext, DynamicApiControllerOptions, DynamicAPIRouteConfig, DynamicApiWebSocketOptions } from '../../interfaces'; +import { DeleteOneRouteConfig, DynamicApiControllerOptions, DynamicApiWebSocketOptions } from '../../interfaces'; import { BaseEntity } from '../../models'; import { DynamicApiBroadcastService } from '../../services'; import { @@ -18,7 +18,7 @@ export class DeleteOneModule { databaseModule: DynamicModule, entity: Type, controllerOptions: DynamicApiControllerOptions, - routeConfig: DynamicAPIRouteConfig, + routeConfig: DeleteOneRouteConfig, version?: string, validationPipeOptions?: ValidationPipeOptions, webSocket?: DynamicApiWebSocketOptions, @@ -41,8 +41,8 @@ export class DeleteOneModule { displayedName, version, routeConfig.callback, - routeConfig.beforeSaveCallback as BeforeSaveDeleteCallback, - routeConfig.beforeDeleteCallback as BeforeDeleteCallback, + routeConfig.beforeSaveCallback, + routeConfig.beforeDeleteCallback, routeConfig.cascade, ); diff --git a/libs/dynamic-api/src/routes/duplicate-many/duplicate-many.module.ts b/libs/dynamic-api/src/routes/duplicate-many/duplicate-many.module.ts index 0b34a5e..9f9808f 100644 --- a/libs/dynamic-api/src/routes/duplicate-many/duplicate-many.module.ts +++ b/libs/dynamic-api/src/routes/duplicate-many/duplicate-many.module.ts @@ -2,7 +2,7 @@ import { DynamicModule, Module, ModuleMetadata, Type, ValidationPipeOptions } fr import { GatewayMetadata } from '@nestjs/websockets'; import { DynamicApiModule } from '../../dynamic-api.module'; import { getDisplayedName, initializeConfigFromOptions } from '../../helpers'; -import { BeforeSaveListCallback, DynamicApiControllerOptions, DynamicAPIRouteConfig, DynamicApiWebSocketOptions } from '../../interfaces'; +import { DuplicateManyRouteConfig, DynamicApiControllerOptions, DynamicApiWebSocketOptions } from '../../interfaces'; import { BaseEntity } from '../../models'; import { DynamicApiBroadcastService } from '../../services'; import { @@ -18,7 +18,7 @@ export class DuplicateManyModule { databaseModule: DynamicModule, entity: Type, controllerOptions: DynamicApiControllerOptions, - routeConfig: DynamicAPIRouteConfig, + routeConfig: DuplicateManyRouteConfig, version?: string, validationPipeOptions?: ValidationPipeOptions, webSocket?: DynamicApiWebSocketOptions, @@ -36,7 +36,7 @@ export class DuplicateManyModule { version, validationPipeOptions, ); - const ServiceProvider = createDuplicateManyServiceProvider(entity, displayedName, version, routeConfig.callback, routeConfig.beforeSaveCallback as BeforeSaveListCallback); + const ServiceProvider = createDuplicateManyServiceProvider(entity, displayedName, version, routeConfig.callback, routeConfig.beforeSaveCallback); const hasBroadcast = !!routeConfig.broadcast; const gatewayOptions = webSocket diff --git a/libs/dynamic-api/src/routes/duplicate-one/duplicate-one.module.ts b/libs/dynamic-api/src/routes/duplicate-one/duplicate-one.module.ts index cab9b4a..9da7c13 100644 --- a/libs/dynamic-api/src/routes/duplicate-one/duplicate-one.module.ts +++ b/libs/dynamic-api/src/routes/duplicate-one/duplicate-one.module.ts @@ -2,7 +2,7 @@ import { DynamicModule, Module, ModuleMetadata, Type, ValidationPipeOptions } fr import { GatewayMetadata } from '@nestjs/websockets'; import { DynamicApiModule } from '../../dynamic-api.module'; import { getDisplayedName, initializeConfigFromOptions } from '../../helpers'; -import { BeforeSaveCallback, DynamicApiControllerOptions, DynamicAPIRouteConfig, DynamicApiWebSocketOptions } from '../../interfaces'; +import { DuplicateOneRouteConfig, DynamicApiControllerOptions, DynamicApiWebSocketOptions } from '../../interfaces'; import { BaseEntity } from '../../models'; import { DynamicApiBroadcastService } from '../../services'; import { @@ -18,7 +18,7 @@ export class DuplicateOneModule { databaseModule: DynamicModule, entity: Type, controllerOptions: DynamicApiControllerOptions, - routeConfig: DynamicAPIRouteConfig, + routeConfig: DuplicateOneRouteConfig, version?: string, validationPipeOptions?: ValidationPipeOptions, webSocket?: DynamicApiWebSocketOptions, @@ -36,7 +36,7 @@ export class DuplicateOneModule { version, validationPipeOptions, ); - const ServiceProvider = createDuplicateOneServiceProvider(entity, displayedName, version, routeConfig.callback, routeConfig.beforeSaveCallback as BeforeSaveCallback); + const ServiceProvider = createDuplicateOneServiceProvider(entity, displayedName, version, routeConfig.callback, routeConfig.beforeSaveCallback); const hasBroadcast = !!routeConfig.broadcast; const gatewayOptions = webSocket diff --git a/libs/dynamic-api/src/routes/replace-one/replace-one.module.ts b/libs/dynamic-api/src/routes/replace-one/replace-one.module.ts index 1072a50..ee77b8b 100644 --- a/libs/dynamic-api/src/routes/replace-one/replace-one.module.ts +++ b/libs/dynamic-api/src/routes/replace-one/replace-one.module.ts @@ -2,7 +2,7 @@ import { DynamicModule, Module, ModuleMetadata, Type, ValidationPipeOptions } fr import { GatewayMetadata } from '@nestjs/websockets'; import { DynamicApiModule } from '../../dynamic-api.module'; import { getDisplayedName, initializeConfigFromOptions } from '../../helpers'; -import { BeforeSaveCallback, DynamicApiControllerOptions, DynamicAPIRouteConfig, DynamicApiWebSocketOptions } from '../../interfaces'; +import { ReplaceOneRouteConfig, DynamicApiControllerOptions, DynamicApiWebSocketOptions } from '../../interfaces'; import { BaseEntity } from '../../models'; import { DynamicApiBroadcastService } from '../../services'; import { @@ -18,7 +18,7 @@ export class ReplaceOneModule { databaseModule: DynamicModule, entity: Type, controllerOptions: DynamicApiControllerOptions, - routeConfig: DynamicAPIRouteConfig, + routeConfig: ReplaceOneRouteConfig, version?: string, validationPipeOptions?: ValidationPipeOptions, webSocket?: DynamicApiWebSocketOptions, @@ -36,7 +36,7 @@ export class ReplaceOneModule { version, validationPipeOptions, ); - const ServiceProvider = createReplaceOneServiceProvider(entity, displayedName, version, routeConfig.callback, routeConfig.beforeSaveCallback as BeforeSaveCallback); + const ServiceProvider = createReplaceOneServiceProvider(entity, displayedName, version, routeConfig.callback, routeConfig.beforeSaveCallback); const hasBroadcast = !!routeConfig.broadcast; const gatewayOptions = webSocket diff --git a/libs/dynamic-api/src/routes/update-many/update-many.module.ts b/libs/dynamic-api/src/routes/update-many/update-many.module.ts index 9409341..7be73b0 100644 --- a/libs/dynamic-api/src/routes/update-many/update-many.module.ts +++ b/libs/dynamic-api/src/routes/update-many/update-many.module.ts @@ -2,7 +2,7 @@ import { DynamicModule, Module, ModuleMetadata, Type, ValidationPipeOptions } fr import { GatewayMetadata } from '@nestjs/websockets'; import { DynamicApiModule } from '../../dynamic-api.module'; import { getDisplayedName, initializeConfigFromOptions } from '../../helpers'; -import { BeforeSaveListCallback, DynamicApiControllerOptions, DynamicAPIRouteConfig, DynamicApiWebSocketOptions } from '../../interfaces'; +import { UpdateManyRouteConfig, DynamicApiControllerOptions, DynamicApiWebSocketOptions } from '../../interfaces'; import { BaseEntity } from '../../models'; import { DynamicApiBroadcastService } from '../../services'; import { @@ -18,7 +18,7 @@ export class UpdateManyModule { databaseModule: DynamicModule, entity: Type, controllerOptions: DynamicApiControllerOptions, - routeConfig: DynamicAPIRouteConfig, + routeConfig: UpdateManyRouteConfig, version?: string, validationPipeOptions?: ValidationPipeOptions, webSocket?: DynamicApiWebSocketOptions, @@ -36,7 +36,7 @@ export class UpdateManyModule { version, validationPipeOptions, ); - const ServiceProvider = createUpdateManyServiceProvider(entity, displayedName, version, routeConfig.callback, routeConfig.beforeSaveCallback as BeforeSaveListCallback); + const ServiceProvider = createUpdateManyServiceProvider(entity, displayedName, version, routeConfig.callback, routeConfig.beforeSaveCallback); const hasBroadcast = !!routeConfig.broadcast; const gatewayOptions = webSocket diff --git a/libs/dynamic-api/src/routes/update-one/update-one.module.ts b/libs/dynamic-api/src/routes/update-one/update-one.module.ts index 139dbc5..193757d 100644 --- a/libs/dynamic-api/src/routes/update-one/update-one.module.ts +++ b/libs/dynamic-api/src/routes/update-one/update-one.module.ts @@ -2,7 +2,7 @@ import { DynamicModule, Module, ModuleMetadata, Type, ValidationPipeOptions } fr import { GatewayMetadata } from '@nestjs/websockets'; import { DynamicApiModule } from '../../dynamic-api.module'; import { getDisplayedName, initializeConfigFromOptions } from '../../helpers'; -import { BeforeSaveCallback, DynamicApiControllerOptions, DynamicAPIRouteConfig, DynamicApiWebSocketOptions } from '../../interfaces'; +import { UpdateOneRouteConfig, DynamicApiControllerOptions, DynamicApiWebSocketOptions } from '../../interfaces'; import { BaseEntity } from '../../models'; import { DynamicApiBroadcastService } from '../../services'; import { createUpdateOneController, createUpdateOneGateway, createUpdateOneServiceProvider } from './update-one.helper'; @@ -14,7 +14,7 @@ export class UpdateOneModule { databaseModule: DynamicModule, entity: Type, controllerOptions: DynamicApiControllerOptions, - routeConfig: DynamicAPIRouteConfig, + routeConfig: UpdateOneRouteConfig, version?: string, validationPipeOptions?: ValidationPipeOptions, webSocket?: DynamicApiWebSocketOptions, @@ -33,7 +33,7 @@ export class UpdateOneModule { validationPipeOptions, ); const ServiceProvider = createUpdateOneServiceProvider( - entity, displayedName, version, routeConfig.callback, routeConfig.beforeSaveCallback as BeforeSaveCallback, + entity, displayedName, version, routeConfig.callback, routeConfig.beforeSaveCallback, ); const hasBroadcast = !!routeConfig.broadcast; diff --git a/libs/dynamic-api/src/version.json b/libs/dynamic-api/src/version.json index fb53be3..6f08b20 100644 --- a/libs/dynamic-api/src/version.json +++ b/libs/dynamic-api/src/version.json @@ -1,3 +1,3 @@ { - "version": "4.12.0" + "version": "4.13.0" } diff --git a/package-lock.json b/package-lock.json index 297c358..1229cdd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "mongodb-dynamic-api", - "version": "4.12.0", + "version": "4.13.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "mongodb-dynamic-api", - "version": "4.12.0", + "version": "4.13.0", "license": "MIT", "dependencies": { "@nestjs/cache-manager": "^3.0.1", diff --git a/package.json b/package.json index 3428053..dc2d2c0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mongodb-dynamic-api", - "version": "4.12.0", + "version": "4.13.0", "description": "Auto generated CRUD API for MongoDB using NestJS", "readmeFilename": "README.md", "main": "index.js",