Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion examples/sample-server/test/zod-full-coverage.e2e-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ import { z } from 'zod';
import {
ExceptionsFilter,
RocketsModule,
defineTypeOrmRepository,
} from '@concepta/rockets';
import { defineTypeOrmRepository } from '@concepta/rockets-repository-typeorm';
import type {
RocketsRepositoryModuleInterface,
SchemaEntityCompiler,
Expand Down
2 changes: 1 addition & 1 deletion examples/sample-server/test/zod-parity.e2e-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ import request from 'supertest';
import {
ExceptionsFilter,
RocketsModule,
defineTypeOrmRepository,
} from '@concepta/rockets';
import { defineTypeOrmRepository } from '@concepta/rockets-repository-typeorm';
import type { ResourceInput } from '@concepta/rockets';
import {
UserMetadataCreateDto,
Expand Down
3 changes: 2 additions & 1 deletion examples/sample-server/test/zod-swagger-golden.e2e-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ import {
SwaggerModule,
} from '@nestjs/swagger';
import { cleanupOpenApiDoc } from 'nestjs-zod';
import { RocketsModule, defineTypeOrmRepository } from '@concepta/rockets';
import { RocketsModule } from '@concepta/rockets';
import { defineTypeOrmRepository } from '@concepta/rockets-repository-typeorm';
import type { ResourceInput } from '@concepta/rockets';
import {
UserMetadataCreateDto,
Expand Down
9 changes: 9 additions & 0 deletions packages/rockets-adapter-firebase/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
# Changelog

## Unreleased

### Changed

- Synchronous `defineFirebaseAuth()` options are now flat
(`defineFirebaseAuth({ firebaseApp })`); asynchronous wiring remains the
explicit `{ forRootAsync }` variant.
- Node.js 20 is the minimum supported runtime.

## 1.0.0-alpha.0

- Initial public alpha release of the Firebase auth adapter.
18 changes: 9 additions & 9 deletions packages/rockets-adapter-firebase/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
> maps the decoded payload to `AuthorizedUser`, and plugs into the standard
> `auth` chain.

**Status:** preview (`0.0.1-dev.0` on npm, dist-tag `alpha`). API expected to
stay shape-compatible through 1.0.
**Status:** pre-1.0 preview (`0.0.1-dev.0`, npm dist-tag `alpha`). Public
shapes may still change before 1.0.

---

Expand Down Expand Up @@ -62,8 +62,8 @@ module wrap the SDK (the common case).
### Wire it into a Rockets app

Use the `defineFirebaseAuth()` helper. It returns an `AuthBootstrap` that
`RocketsModule.forRoot({ auth })` consumes directly. When `forRoot()` /
`forRootAsync()` is set, core imports `FirebaseAuthModule` and injects
`createServer({ auth })` or `RocketsModule.forRoot({ auth })` consumes
directly. Core imports `FirebaseAuthModule` and injects
`FirebaseAuthAdapter` from that module — the adapter is not double-registered.

```typescript
Expand All @@ -80,7 +80,7 @@ const firebaseApp = initializeApp({ credential: applicationDefault() });
imports: [
RocketsModule.forRoot({
auth: defineFirebaseAuth({
forRoot: { firebaseApp },
firebaseApp,
}),
userMetadata: {
/* entity, createDto, updateDto */
Expand All @@ -93,8 +93,8 @@ const firebaseApp = initializeApp({ credential: applicationDefault() });
export class AppModule {}
```

Pass `forRootAsync` instead of `forRoot` to build options asynchronously (e.g.
inject `ConfigService`). See
Pass `{ forRootAsync: ... }` instead of flat sync options to build options
asynchronously (e.g. inject `ConfigService`). See
[How-to › Build options asynchronously](#build-options-asynchronously).

---
Expand Down Expand Up @@ -203,8 +203,8 @@ export class ProfileController {

| Member | Purpose |
|---|---|
| `defineFirebaseAuth(input)` | Returns an `AuthBootstrap` for `RocketsModule.forRoot({ auth })`. Accepts `{ forRoot }` for sync or `{ forRootAsync }` for async wiring. Auth-owned entities belong on app `resources[]`, not on this helper. |
| `DefineFirebaseAuthInput` | Discriminated input type — pass exactly one of `forRoot` / `forRootAsync`. |
| `defineFirebaseAuth(input)` | Returns an `AuthBootstrap` for `createServer({ auth })` or `RocketsModule.forRoot({ auth })`. Pass module options directly for sync wiring, or `{ forRootAsync }` for async wiring. |
| `DefineFirebaseAuthInput` | Input type for either flat sync options or the explicit `{ forRootAsync }` variant. |

### Module

Expand Down
3 changes: 1 addition & 2 deletions packages/rockets-adapter-firebase/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,7 @@
"directory": "packages/rockets-adapter-firebase"
},
"engines": {
"node": ">=18.0.0",
"npm": ">=8.0.0"
"node": ">=20.0.0"
},
"files": [
"dist/**/!(*.spec|*.e2e-spec|*.fixture).{js,d.ts}",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ class FakeVerifier implements FirebaseTokenVerifierInterface {
describe('defineFirebaseAuth', () => {
it('returns AuthBootstrap with FirebaseAuthAdapter and forRoot (sync path)', () => {
const bootstrap = defineFirebaseAuth({
forRoot: { verifier: FakeVerifier },
verifier: FakeVerifier,
});

expect(bootstrap.adapter).toBe(FirebaseAuthAdapter);
Expand All @@ -36,4 +36,35 @@ describe('defineFirebaseAuth', () => {
const dynamicModule = bootstrap.forRoot!();
expect(dynamicModule.module).toBe(FirebaseAuthModule);
});

it('accepts every sync option, including the inherited `imports`', () => {
class SyncSideModule {}

const bootstrap = defineFirebaseAuth({
verifier: FakeVerifier,
imports: [SyncSideModule],
});

expect(bootstrap.forRoot!().module).toBe(FirebaseAuthModule);
});

it('rejects sync options alongside `forRootAsync`', () => {
// `forRoot()` only forwards `input.forRootAsync`, so a sync key here would
// be silently dropped. The exclusion is derived from
// `keyof FirebaseAuthModuleOptions`, so it covers the inherited `imports`
// too — these two directives fail the build if that hole ever reopens.
// @ts-expect-error `imports` belongs to the sync branch
defineFirebaseAuth({
forRootAsync: { useFactory: () => ({ verifier: FakeVerifier }) },
imports: [class AsyncSideModule {}],
});

// @ts-expect-error `verifier` belongs to the sync branch
defineFirebaseAuth({
forRootAsync: { useFactory: () => ({ verifier: FakeVerifier }) },
verifier: FakeVerifier,
});

expect(true).toBe(true);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ describe('FirebaseAuthAdapter + AuthServerGuard (integration)', () => {

beforeAll(async () => {
const bootstrap = defineFirebaseAuth({
forRoot: { verifier: StubVerifier },
verifier: StubVerifier,
});

const moduleRef = await Test.createTestingModule({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,23 @@ import { FirebaseAuthModule } from '../modules/firebase-auth.module';
* Input for {@link defineFirebaseAuth}.
*
* Choose exactly one wiring shape:
* - `forRoot` — sync options (`FirebaseAuthModule.forRoot` payload).
* - flat sync options (`FirebaseAuthModule.forRoot` payload).
* - `forRootAsync` — async options (`FirebaseAuthModule.forRootAsync` payload).
*
* Auth-owned entities belong in app `resources[]`, not here.
*/
export type DefineFirebaseAuthInput =
| Readonly<{
forRoot: FirebaseAuthModuleOptions;
| (Readonly<FirebaseAuthModuleOptions> & {
forRootAsync?: never;
}>
| Readonly<{
forRootAsync: FirebaseAuthModuleAsyncOptions;
forRoot?: never;
}>;
})
// The sync keys are derived from `FirebaseAuthModuleOptions` rather than
// listed by hand, so adding an option cannot silently leave a hole that
// lets a sync key ride along with `forRootAsync` and get dropped.
| Readonly<
{
forRootAsync: FirebaseAuthModuleAsyncOptions;
} & Partial<Record<keyof FirebaseAuthModuleOptions, never>>
>;

/**
* Build an {@link AuthBootstrap} that wires `FirebaseAuthModule` into core.
Expand All @@ -33,8 +36,8 @@ export function defineFirebaseAuth(
return {
adapter: FirebaseAuthAdapter,
forRoot: () =>
input.forRootAsync !== undefined
'forRootAsync' in input && input.forRootAsync !== undefined
? FirebaseAuthModule.forRootAsync(input.forRootAsync)
: FirebaseAuthModule.forRoot(input.forRoot),
: FirebaseAuthModule.forRoot(input),
};
}
27 changes: 27 additions & 0 deletions packages/rockets-core/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Changelog

## Unreleased

### Added

- `AuthBootstrapContributions`, allowing an auth integration to carry its owned
resources, metadata contract, repository, and guard preference.
- `defineAuthAdapter()`, which registers and exports a custom auth adapter from
a generated host module.

### Changed

- `AuthServerGuard` recognizes the upstream class-level public-route sentinel.
- The built-in user-metadata CQRS handlers are registered per handler: each one
is used only when `userMetadata` is configured or that specific handler is
overridden through `handlers`. Previously, overriding one handler also pulled
in the other built-in, which fails to resolve the user-metadata repository
when no metadata contract exists.
- Node.js 20 is the minimum supported runtime.

### Removed

- `createStubAuthBootstrap()`. It had become an alias for `defineAuthAdapter()`,
which produces the same host module and additionally accepts imports,
controllers, providers, exports, and `contributes`. Replace
`createStubAuthBootstrap(Adapter)` with `defineAuthAdapter(Adapter)`.
20 changes: 9 additions & 11 deletions packages/rockets-core/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
> Configuration-driven composition layer: one options object → planner →
> upstream `@concepta/nestjs-*` modules registered as Nest imports.

**Status:** stable (`0.0.1-dev.0` on npm, dist-tag `alpha`).
**Status:** pre-1.0 preview (`0.0.1-dev.0`, npm dist-tag `alpha`).

---

Expand Down Expand Up @@ -105,16 +105,17 @@ import { APP_GUARD } from '@nestjs/core';
import {
RocketsCoreModule,
AuthServerGuard,
defineAuthAdapter,
defineResource,
} from '@concepta/rockets-core';
import { JwtAdapter } from './auth/jwt.adapter';
import { PetEntity } from './pet.entity';
import { defineTypeOrmRepository } from './repository/define-typeorm-repository';
import { defineTypeOrmRepository } from '@concepta/rockets-repository-typeorm';

@Module({
imports: [
RocketsCoreModule.forRoot({
auth: JwtAdapter,
auth: defineAuthAdapter(JwtAdapter),
repository: defineTypeOrmRepository({
type: 'sqlite',
database: ':memory:',
Expand All @@ -130,18 +131,15 @@ export class AppModule {}

### What just happened

- `auth: JwtAdapter` registered the adapter as a provider; core exposes the
ordered chain on `AUTH_ADAPTERS_TOKEN` for `AuthServerGuard`.
- `defineAuthAdapter(JwtAdapter)` registered and exported the adapter; core
exposes the ordered chain on `AUTH_ADAPTERS_TOKEN` for `AuthServerGuard`.
- `repository: defineTypeOrmRepository(...)` is the only place that mentions
TypeORM. The planner collects entities from `resources[]` and registers them.
- `defineResource({ entity: PetEntity })` produced `GET/POST/PATCH/DELETE /pets`
with validation and Swagger schema. No controller was written.

`defineTypeOrmRepository` is a small app-local `RepositoryBootstrap` wrapper
(TypeORM connection options + planner-derived entity list) around
`TypeOrmRepositoryModule` from `@concepta/rockets-repository-typeorm`. Keep the
helper in the sample app (or copy into yours) — do not pull TypeORM into
`@concepta/rockets-core` itself.
`defineTypeOrmRepository` is owned by
`@concepta/rockets-repository-typeorm`; core remains storage-agnostic.

---

Expand Down Expand Up @@ -487,7 +485,7 @@ expect.

| Option | Type | Required | Description |
| -------------- | ---------------------------------------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `auth` | `AuthBootstrap` or array | optional† | Auth wiring from `defineFirebaseAuth()`, `defineRocketsAuth()`, or app-local helpers. Each entry supplies `adapter` and optional `forRoot()` for external Nest modules. Entity rows belong in `resources[]`, not on the auth helper. |
| `auth` | `AuthBootstrap` or array | optional† | Auth wiring from `defineFirebaseAuth()`, `defineRocketsAuth()`, or app-local helpers. Each entry supplies an `adapter`, optional `forRoot()`, and optional integration-owned defaults through `contributes`; explicit app options win. |
| `repository` | `RepositoryModuleInterface` or `RepositoryBootstrap` | optional | Default persistence adapter. A bootstrap owns both `forRoot(entities)` and `forFeature(entities)`. |
| `userMetadata` | `RocketsUserMetadataConfig` | optional | Entity + DTOs for the metadata table joined to external users. |
| `resources` | `ReadonlyArray<ResourceInput>` | optional | Mix of `defineResource`, `defineModuleResource`, and manual `RocketsResourceConfig`. |
Expand Down
3 changes: 1 addition & 2 deletions packages/rockets-core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,7 @@
"directory": "packages/rockets-core"
},
"engines": {
"node": ">=18.0.0",
"npm": ">=8.0.0"
"node": ">=20.0.0"
},
"files": [
"dist/**/!(*.spec|*.e2e-spec|*.fixture).{js,d.ts}",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ import { USER_METADATA_MODULE_ENTITY_KEY } from '../rockets-core.constants';
import { APP_GUARD } from '@nestjs/core';
import { AuthServerGuard } from '../infrastructure/guards/auth-server.guard';
import { defineResource } from '../infrastructure/resource/define-resource';
import { createStubAuthBootstrap } from '../infrastructure/auth/create-stub-auth-bootstrap';
import { defineAuthAdapter } from '../infrastructure/auth/define-auth-adapter';

// ── Fixtures ──

Expand Down Expand Up @@ -147,7 +147,7 @@ describe('RocketsCoreModule — opt-in accessControl (e2e)', () => {
}),
MetaRepoModule,
RocketsCoreModule.forRoot({
auth: createStubAuthBootstrap(RoleAuthProvider),
auth: defineAuthAdapter(RoleAuthProvider),
providers: [RoleAuthProvider],
repository: TypeOrmRepositoryModule,
resources: [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ import { USER_METADATA_MODULE_ENTITY_KEY } from '../rockets-core.constants';
import { AuthServerGuard } from '../infrastructure/guards/auth-server.guard';
import { RocketsCoreExceptionsFilter } from '../infrastructure/filters/exceptions.filter';
import { defineResource } from '../infrastructure/resource/define-resource';
import { createStubAuthBootstrap } from '../infrastructure/auth/create-stub-auth-bootstrap';
import { defineAuthAdapter } from '../infrastructure/auth/define-auth-adapter';
import { defineHook } from '../infrastructure/hooks/define-hook';

// ── Auth fixture ──
Expand Down Expand Up @@ -200,7 +200,7 @@ describe('defineHook — functional entity hook (e2e)', () => {
}),
MetaModule,
RocketsCoreModule.forRoot({
auth: createStubAuthBootstrap(StubAuthAdapter),
auth: defineAuthAdapter(StubAuthAdapter),
providers: [StubAuthAdapter],
repository: TypeOrmRepositoryModule,
resources: [thingResource, otherResource],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ import { USER_METADATA_MODULE_ENTITY_KEY } from '../rockets-core.constants';
import { AuthServerGuard } from '../infrastructure/guards/auth-server.guard';
import { defineResource } from '../infrastructure/resource/define-resource';
import { defineModuleResource } from '../infrastructure/resource/define-module-resource';
import { createStubAuthBootstrap } from '../infrastructure/auth/create-stub-auth-bootstrap';
import { defineAuthAdapter } from '../infrastructure/auth/define-auth-adapter';
import { InjectDynamicRepository } from '../common';
import {
EntityHook,
Expand Down Expand Up @@ -243,7 +243,7 @@ describe('@EntityHook({ entity }) — runtime binding (e2e)', () => {
}),
MetaModule,
RocketsCoreModule.forRoot({
auth: createStubAuthBootstrap(StubAuthAdapter),
auth: defineAuthAdapter(StubAuthAdapter),
providers: [StubAuthAdapter],
repository: TypeOrmRepositoryModule,
resources: [widgetResource, gadgetResource, widgetLogFeature],
Expand Down
Loading
Loading