From 260df73b0a478863b0b55ba69d3ecb0088b9cbe0 Mon Sep 17 00:00:00 2001 From: Leo Farias Date: Fri, 7 Aug 2026 14:40:07 -0400 Subject: [PATCH 1/7] feat: prepare Rockets for 1.0 preview --- .github/ISSUE_TEMPLATE/bug_report.yml | 2 +- CONFIGURATION.md | 131 ++++++------ MIGRATION-SUMMARY.md | 123 ----------- README.md | 194 ++++++++---------- examples/sample-code-review/README.md | 4 +- .../sample-code-review/apps/api/package.json | 2 +- .../apps/api/src/app.module.ts | 2 +- .../src/auth-api-key/define-api-key-auth.ts | 14 +- .../repository/define-typeorm-repository.ts | 27 --- examples/sample-server-auth/README.md | 14 +- examples/sample-server-auth/package.json | 2 +- examples/sample-server-auth/src/app.module.ts | 18 +- .../repository/define-typeorm-repository.ts | 47 ----- examples/sample-server/README.md | 1 - examples/sample-server/package.json | 2 +- examples/sample-server/src/app.module.ts | 58 +++--- .../src/auth/define-sample-auth.ts | 14 +- examples/sample-server/src/main.ts | 4 +- .../repository/define-typeorm-repository.ts | 46 ----- .../test/zod-full-coverage.e2e-spec.ts | 2 +- .../sample-server/test/zod-parity.e2e-spec.ts | 2 +- .../test/zod-swagger-golden.e2e-spec.ts | 3 +- package.json | 13 +- .../rockets-adapter-firebase/CHANGELOG.md | 9 + packages/rockets-adapter-firebase/README.md | 18 +- .../rockets-adapter-firebase/package.json | 3 +- .../__tests__/define-firebase-auth.spec.ts | 2 +- .../firebase-auth-guard.integration.spec.ts | 2 +- .../src/integration/define-firebase-auth.ts | 16 +- packages/rockets-core/CHANGELOG.md | 15 ++ packages/rockets-core/README.md | 20 +- packages/rockets-core/package.json | 3 +- .../src/__e2e__/rockets-core.e2e-spec.ts | 6 + .../interfaces/auth-bootstrap.interface.ts | 17 ++ packages/rockets-core/src/index.ts | 7 +- .../auth/create-stub-auth-bootstrap.ts | 10 +- .../auth/define-auth-adapter.spec.ts | 49 +++++ .../auth/define-auth-adapter.ts | 40 ++++ .../guards/auth-server.guard.ts | 7 +- .../src/rockets-core.module-definition.ts | 12 +- .../rockets-repository-firestore/CHANGELOG.md | 11 +- .../rockets-repository-firestore/README.md | 6 +- .../rockets-repository-firestore/package.json | 6 +- .../firestore-repository.module.spec.ts | 169 +++++++++++++++ .../firestore-where.translator.spec.ts | 27 +++ .../src/backends/admin-firestore.backend.ts | 79 +++---- .../backends/in-memory-firestore.backend.ts | 125 +++-------- .../define-firestore-repository.ts | 2 +- .../interfaces/firestore-backend.interface.ts | 6 + .../src/repository/firestore-query-runner.ts | 49 +---- .../src/repository/firestore-repository.ts | 4 +- .../src/repository/firestore-row-filter.ts | 56 +++++ .../src/repository/firestore-sort.ts | 28 +++ .../src/repository/firestore-value.ts | 25 +++ .../repository/firestore-where.translator.ts | 34 ++- .../rockets-repository-typeorm/CHANGELOG.md | 12 ++ packages/rockets-repository-typeorm/README.md | 30 ++- .../rockets-repository-typeorm/package.json | 7 +- .../src}/define-typeorm-repository.spec.ts | 33 +-- .../src}/define-typeorm-repository.ts | 8 +- .../rockets-repository-typeorm/src/index.ts | 1 + packages/rockets-server-auth/CHANGELOG.md | 34 ++- packages/rockets-server-auth/README.md | 81 ++++---- packages/rockets-server-auth/SWAGGER.md | 87 -------- .../bin/generate-swagger.js | 13 -- packages/rockets-server-auth/package.json | 14 +- .../helpers/rockets-auth-e2e-app.factory.ts | 12 +- .../src/define-rockets-auth.spec.ts | 54 +++++ .../src/define-rockets-auth.ts | 21 +- .../auth/__tests__/auth-readiness.e2e-spec.ts | 130 ++++++++++++ .../rockets-auth-recovery.controller.ts | 132 ++++++++++++ .../rockets-auth-token.controller.ts | 2 +- .../src/domains/auth/index.ts | 1 + .../factories/build-invitation-controllers.ts | 2 +- .../build-rockets-auth-otp-controller.ts | 2 +- .../rockets-auth-handler-overrides.spec.ts | 35 ++-- .../modules/rockets-auth-signup.module.ts | 3 +- packages/rockets-server-auth/src/index.ts | 2 +- .../src/rockets-auth.module-definition.ts | 52 ++++- .../rockets-auth-password-port.handlers.ts | 79 ++++++- ...lidate-current-password-override.module.ts | 19 -- ...-validate-current-password.handler.spec.ts | 17 +- ...ckets-validate-current-password.handler.ts | 37 +++- .../rockets-auth-options-extras.interface.ts | 10 + packages/rockets-server/CHANGELOG.md | 142 ++----------- packages/rockets-server/README.md | 98 +++++---- packages/rockets-server/SWAGGER.md | 20 -- packages/rockets-server/package.json | 27 +-- .../src/__e2e__/create-server.e2e-spec.ts | 108 ++++++++++ .../rockets-server/src/create-server.spec.ts | 29 +++ packages/rockets-server/src/create-server.ts | 21 ++ packages/rockets-server/src/index.ts | 5 +- .../src/rockets.module-definition.spec.ts | 166 +++++++++++++-- .../src/rockets.module-definition.ts | 104 +++++++--- packages/rockets-server/src/typeorm.ts | 1 - packages/rockets-server/tsconfig.json | 3 +- scripts/verify-package-artifacts.mjs | 103 ++++++++++ yarn.lock | 33 +-- 98 files changed, 2062 insertions(+), 1316 deletions(-) delete mode 100644 MIGRATION-SUMMARY.md delete mode 100644 examples/sample-code-review/apps/api/src/repository/define-typeorm-repository.ts delete mode 100644 examples/sample-server-auth/src/repository/define-typeorm-repository.ts delete mode 100644 examples/sample-server/src/repository/define-typeorm-repository.ts create mode 100644 packages/rockets-core/CHANGELOG.md create mode 100644 packages/rockets-core/src/infrastructure/auth/define-auth-adapter.spec.ts create mode 100644 packages/rockets-core/src/infrastructure/auth/define-auth-adapter.ts create mode 100644 packages/rockets-repository-firestore/src/repository/firestore-row-filter.ts create mode 100644 packages/rockets-repository-firestore/src/repository/firestore-sort.ts create mode 100644 packages/rockets-repository-firestore/src/repository/firestore-value.ts create mode 100644 packages/rockets-repository-typeorm/CHANGELOG.md rename packages/{rockets-server/src/repository => rockets-repository-typeorm/src}/define-typeorm-repository.spec.ts (60%) rename packages/{rockets-server/src/repository => rockets-repository-typeorm/src}/define-typeorm-repository.ts (72%) delete mode 100644 packages/rockets-server-auth/SWAGGER.md delete mode 100755 packages/rockets-server-auth/bin/generate-swagger.js create mode 100644 packages/rockets-server-auth/src/define-rockets-auth.spec.ts create mode 100644 packages/rockets-server-auth/src/domains/auth/__tests__/auth-readiness.e2e-spec.ts create mode 100644 packages/rockets-server-auth/src/domains/auth/gateways/http/controllers/rockets-auth-recovery.controller.ts delete mode 100644 packages/rockets-server-auth/src/shared/authentication/rockets-validate-current-password-override.module.ts delete mode 100644 packages/rockets-server/SWAGGER.md create mode 100644 packages/rockets-server/src/__e2e__/create-server.e2e-spec.ts create mode 100644 packages/rockets-server/src/create-server.spec.ts create mode 100644 packages/rockets-server/src/create-server.ts delete mode 100644 packages/rockets-server/src/typeorm.ts create mode 100644 scripts/verify-package-artifacts.mjs diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 3b3f4f651..693c19c36 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -55,4 +55,4 @@ body: - type: input attributes: label: NestJS version - placeholder: "^11.0.0" + placeholder: "12.0.0-alpha.5" diff --git a/CONFIGURATION.md b/CONFIGURATION.md index d35552996..1e4ca5aa8 100644 --- a/CONFIGURATION.md +++ b/CONFIGURATION.md @@ -1,7 +1,7 @@ # Rockets — Configuration Entry Point -> Configuration reference for the **v2 DSL** (shipped). Field names below match -> the current `packages/*/src/**` (code wins over READMEs). The v2 redesign +> Configuration reference for the **1.0-preview DSL**. Field names below match +> the current `packages/*/src/**` (code wins over prose). The original DSL > rationale, convertibility proof, and change-set live in §12. > Diagrams are Mermaid — they render on GitHub and in most Markdown viewers. @@ -11,10 +11,11 @@ You never hand NestJS a tree of modules. You write **declarative bundles** (`defineResource`, `defineSubResource`, `defineModuleResource`) and a few -top-level fields (`repository`, `userMetadata`, `auth`), pass them to **one** -`RocketsModule.forRoot({...})`, and the module-definition transform converts -that into a single global `DynamicModule` (controllers, providers, repository -tokens, CQRS handlers, Swagger). +top-level fields (`repository`, `userMetadata`, `auth`), pass them to +`createServer({...})`, and the module-definition transform converts that into a +single global `DynamicModule` (controllers, providers, repository tokens, CQRS +handlers, Swagger). Use `RocketsModule.forRoot({...})` directly when a larger +Nest host module must compose Rockets with other imports or providers. ```mermaid flowchart LR @@ -24,7 +25,8 @@ flowchart LR M["defineModuleResource()"] OPT["repository / userMetadata / auth / swagger"] end - YOU --> FORROOT["RocketsModule.forRoot( ... )"] + YOU --> CREATE["createServer( ... )"] + CREATE --> FORROOT["RocketsModule.forRoot( ... )"] FORROOT --> XFORM["definitionTransform\n(build time)"] XFORM --> PLAN["buildAppRegistrationPlan()"] PLAN --> DM["one global DynamicModule\nimports / providers / controllers / exports"] @@ -34,12 +36,13 @@ flowchart LR **Two layers, one surface.** `@concepta/rockets` (server) is a thin presentation layer over `@concepta/rockets-core`. Server adds the `MeController`, the global guard opt-in, and the `auth` chain; core does the actual resource→module -conversion. You only ever call `RocketsModule.forRoot` (server). Core's -`forRootAsync` is called internally. +conversion. `createServer` is the canonical definition-first facade; +`RocketsModule.forRoot` is the lower-level composition surface. Core's +`forRootAsync` is called internally in either case. --- -## 1. The entry point — `RocketsModule.forRoot` / `forRootAsync` +## 1. The entry point — `createServer` and `RocketsModule` The options object is split by NestJS's `ConfigurableModuleBuilder` into two buckets with very different lifecycles: @@ -58,20 +61,24 @@ buckets with very different lifecycles: | Field | Type | Req? | Default | Purpose | |---|---|---|---|---| | `resources` | `ReadonlyArray` | optional | `[]` | The feature bundles (CRUD + module + sub flattened). | -| `repository` | `RepositoryModuleInterface \| RepositoryBootstrap` | optional* | — | Root persistence adapter (TypeORM/Firestore/…). | -| `userMetadata` | `RocketsUserMetadataConfig` | **required at runtime** | — | `/me` entity + DTOs. Omit → throws when the metadata DTO token resolves. | +| `repository` | `RepositoryModuleInterface \| RepositoryBootstrap` | optional† | — | Root persistence adapter (TypeORM/Firestore/…). | +| `userMetadata` | `RocketsUserMetadataConfig` | optional | — | `/me` entity + DTOs. When omitted, Rockets does not mount `/me` or register metadata handlers/providers. | | `auth` | `AuthBootstrap \| AuthBootstrap[]` | optional | `[]` | Auth chain (external adapter and/or built-in). | | `swagger` | `SwaggerUiOptionsInterface` | optional | — | Doc builder + UI. The **only** runtime field forwarded to core. | | `settings` | `RocketsSettingsInterface` (empty today) | optional | — | Reserved; no fields yet. | | `handlers` | `{ upsertUserMetadata?, getUserMetadata? }` | optional | built-ins | Override the user-metadata CQRS handlers. | -| `enableGlobalGuard` | `boolean` | optional | **on** (opt-out) | Register `AuthServerGuard` as `APP_GUARD` unless `=== false`. | +| `enableGlobalGuard` | `boolean` | optional | **on‡** | Register `AuthServerGuard` as `APP_GUARD` unless `=== false`. | | `disableController` | `{ me?: boolean }` | optional | `{}` | Disable built-in `MeController`. | | `controllers` | `DynamicModule['controllers']` | optional | — | Replace the auto controller set. | | `global` | `boolean` | optional | **forced `true`** | `forRoot` always makes the module global. | -\* `repository` is optional in the type but persistence resolution throws if an +† `repository` is optional in the type but persistence resolution throws if an entity has neither a per-entity override nor a root adapter. +‡ The built-in `defineRocketsAuth()` integration contributes `false` because +its upstream authentication module already owns a JWT global guard. Explicit +server options always win; mixed-auth hosts can opt the Rockets chain back in. + `*-server` / `*-core` split — what server forwards vs keeps: ```mermaid @@ -436,9 +443,19 @@ accepts one bootstrap or a **chain** (array, tried in order). interface AuthBootstrap { adapter: Type; forRoot?: () => DynamicModule; // host module: provides+exports the adapter + contributes?: { // integration-owned app defaults + resources?: ReadonlyArray; + userMetadata?: RocketsUserMetadataConfig; + repository?: RepositoryModuleInterface | RepositoryBootstrap; + enableGlobalGuard?: boolean; + }; } ``` +Explicit server options override contributed defaults. Resource contributions +are prepended to application resources; incompatible single-value defaults from +multiple auth integrations fail fast instead of depending on import order. + ```mermaid flowchart TD REQ["incoming request"] --> GUARD["AuthServerGuard (APP_GUARD)"] @@ -469,33 +486,26 @@ interface AuthAdapterInterface { ``` **Global guard (default-on / opt-out):** `AuthServerGuard` is registered as -`APP_GUARD` **unless `enableGlobalGuard === false`** — enabled by default; you -opt **out**, never in. Routes are guarded unless explicitly made public -(`@AuthPublic()`) or the global guard is disabled. +`APP_GUARD` **unless `enableGlobalGuard === false`**. Auth integrations may +contribute a different default; explicit app configuration wins. Routes are +guarded unless explicitly made public (`@AuthPublic()`) or the global guard is +disabled. ### 7a. External auth (`@concepta/rockets`) — you own `authenticate()` -Minimum (core stub shape): +Minimum: ```ts -function createStubAuthBootstrap(adapter) { - return { adapter, forRoot: () => ({ module: class {}, providers: [adapter], exports: [adapter] }) }; -} +const auth = defineAuthAdapter(MyAuthAdapter); ``` Complete (`examples/sample-server/src/auth/define-sample-auth.ts`): ```ts export function defineSampleAuth(): AuthBootstrap { - return { - adapter: SampleAuthAdapter, - forRoot: () => ({ - module: class SampleAuthHostModule {}, - providers: [SampleAuthAdapter], - controllers: [AuthController], - exports: [SampleAuthAdapter], // controller + entity stay internal - }), - }; + return defineAuthAdapter(SampleAuthAdapter, { + controllers: [AuthController], // controller stays integration-private + }); } RocketsModule.forRoot({ @@ -533,10 +543,10 @@ Concept → field map: |---|---| | JWT secrets/signing | `authentication.settings.jwt.{access,refresh}` | | login/strategies | `authentication.settings.strategies` | -| recovery | `authentication.ports.recoveryNotification` (**required**) + `verifyNotification` | +| recovery | `/recovery/*` controllers (enabled by default) + required `authentication.ports.recoveryNotification`; verification uses `verifyNotification` | | otp | `otp` block + `settings.otp` + `disableController.otp` | | signup / admin | `userCrud` (+ `handlers.*`) + `disableController.{signup,admin}` | -| oauth / federated | `federated` block — **OAuth provider modules are NOT in v8 yet (G1 gap)** | +| oauth / federated | `federated` persistence block; OAuth provider routes are deferred from the current 1.0 scope | Complete (`examples/sample-server-auth/src/app.module.ts`): @@ -558,20 +568,20 @@ const rocketsAuthInput: DefineRocketsAuthInput = { }; const rocketsAuth = defineRocketsAuth(rocketsAuthInput); -const rocketsAuthResources = buildRocketsAuthResources(rocketsAuthInput.persistence, rocketsAuthInput.invitationEntity); RocketsModule.forRoot({ auth: rocketsAuth, - userMetadata: rocketsAuthInput.userMetadata, - enableGlobalGuard: false, // auth uses per-controller guards - repository: repo, // SAME instance as persistence.module (reference equality!) - resources: [ ...rocketsAuthResources, createPetResource(), /* … */ ], + resources: [createPetResource(), /* … */], }); ``` -> **Reference-equality trap:** the `repo` passed to `RocketsModule.repository` -> and to `defineRocketsAuth({ persistence: { module: repo } })` must be the -> **same object** — entities are grouped per adapter by identity. +`defineRocketsAuth` contributes its persistence resources, metadata contract, +repository bootstrap, and guard preference to the surrounding server. The host +only declares application-owned resources. Explicit server options remain the +escape hatch and take precedence over those contributed defaults. Its Rockets +guard preference is `false` because `AuthenticationModule` already owns the JWT +global guard; mixed-auth hosts can set `rocketsDefaults.enableGlobalGuard: true` +and `auth.appGuard: false` to make the ordered Rockets adapter chain the owner. --- @@ -596,16 +606,16 @@ Minimum: repository: defineTypeOrmRepository({ type: 'sqlite', database: ':memory:', synchronize: true }) ``` -Selecting TypeORM (`examples/sample-server/src/repository/define-typeorm-repository.ts`): +Selecting TypeORM: ```ts -export function defineTypeOrmRepository(connection): RepositoryBootstrap { - return { - name: 'typeorm-bootstrap', - forFeature: (entities) => TypeOrmRepositoryModule.forFeature(entities), // one repo token per key - forRoot: (entities) => TypeOrmModule.forRoot({ ...connection, entities: [...entities] }), - }; -} +import { defineTypeOrmRepository } from '@concepta/rockets-repository-typeorm'; + +const repository = defineTypeOrmRepository({ + type: 'sqlite', + database: ':memory:', + synchronize: true, +}); ``` Swap to Firestore = pass a `defineFirestoreRepository(...)` instead — **no @@ -631,7 +641,7 @@ interface RocketsUserMetadataConfig { } ``` -Minimum (required at runtime — omit and the metadata DTO token throws): +Enable the optional `/me` surface by supplying: ```ts userMetadata: { @@ -681,13 +691,10 @@ flowchart TD `sampleAuthUserResource` + `defineSampleAuth`). 3. **`SafeCrudContextInterceptor`** — a live workaround replacing upstream's global `CrudContextOverlay`; flagged for removal once upstream is mixed-app safe. -4. **OAuth (G1)** — federated/OAuth provider modules are not ported to v8 yet. +4. **OAuth** — federated identity persistence exists, but provider-specific + OAuth routes are deferred from the current 1.0 scope. 5. **`settings`** — both server and core `settings` are empty interfaces today (reserved slot). -6. **Pre-existing e2e/typecheck gaps on this branch** (independent of the v2 - work): `rockets-crud` photo-CRUD e2e ×2 + `rockets-server-auth` password e2e - ×2 fail (31/35); `sample-server` has 2 `CrudCommandHandler` ctor typecheck - errors and `sample-server-auth` 1 deep crud-import error. --- @@ -697,11 +704,12 @@ flowchart TD ## 12. Signature v2 — design rationale & change-set (SHIPPED) -> **Status: implemented.** The v2 DSL described in §1–§10 is live in -> `packages/rockets-core/src/**`, with both sample apps migrated. Verified: -> build green, core unit 296/296, core e2e 47/47, package e2e 31/35 (the 4 -> failures pre-date this work). This section keeps the *why* — the constraint, -> the convertibility proof, the locked naming, and the change-set. +> **Status: implemented.** The DSL described in §1–§10 is live in +> `packages/rockets-core/src/**`, with all sample apps migrated. The root +> `release:check` gate verifies builds, spec typechecking, code and Markdown +> linting, unit and package E2E tests, sample builds/E2E tests, and dry-run +> package artifacts. This section keeps the *why* — the constraint, the +> convertibility proof, the locked naming, and the change-set. > > **Constraint:** "no breaking" meant **no functional / feature regression** — > NOT "cannot change the entry config". The input DSL was ours to redesign; the @@ -746,8 +754,9 @@ do not present join as the parent-child association mechanism. - **Per-operation DTOs: `input` / `output`.** Not `body` (write-only word) and not `request` — `request` is already the `{ params, body, bodyBatch, validation }` envelope in crud, so it is taken. `input → request.body`, `output → response.resource`. -- **`repository` everywhere.** Single name for "which adapter": root, per-resource, - per-entity, `userMetadata`. Replaces `persistence.module`. +- **`repository` at the application/resource layer.** Single name for "which + adapter": root, per-resource, per-entity, and `userMetadata`. Built-in auth + retains `persistence.module` because that input also owns the auth entity map. ### 12.3 Final signatures diff --git a/MIGRATION-SUMMARY.md b/MIGRATION-SUMMARY.md deleted file mode 100644 index fffa2ea76..000000000 --- a/MIGRATION-SUMMARY.md +++ /dev/null @@ -1,123 +0,0 @@ -# @bitwild Self-Contained Migration — Session Summary - -**Branch:** `feature/module-migration` -**Date:** 2026-06-04 -**Status:** build GREEN · lint PASS · e2e 31/35 suites (248 tests) · NOT committed - ---- - -## Goal - -Turn the `@bitwild` repository / crud / app packages from **thin wrappers over -upstream `@concepta/nestjs-*`** into **self-contained source**, by adopting the -original concepta source packages (copied in as `*-concepta` folders) and -deleting the wrappers. Consolidate `rockets-common` into `rockets-app`. - -## End-state package layout - -```text -app ← repository ← crud (lowest → highest layer) - ↑ ↖ - repository-typeorm core ← server / server-auth -``` - -| Package | What it is now | -|---|---| -| `@concepta/rockets-app` | **Foundation/kernel.** Context overlay (`AppContextHost`, `getAppContext`, `OverlayRef`, `Ctx`, `ContextOverlayInterceptor`), `RuntimeException`, hooks (`HookResolverService`, `Spec`), references, audit, `DomainAggregate`, `AuthUser`, SwaggerUi module, utils (`deriveEntityKey`/`resolveEntityKey`, `createRepositoryContext`, `whitelistedFromDto`, `stripUndefined`). **Replaced `rockets-common` (deleted).** Zero `@concepta/nestjs-*` deps. | -| `@concepta/rockets-repository` | Self-contained dynamic repository (module, adapter, transactions, federation, hooks, query helpers). DB-agnostic. `@InjectDynamicRepository(string \| Type)`. | -| `@concepta/rockets-repository-typeorm` | TypeORM implementation. | -| `@concepta/rockets-crud` | Self-contained CRUD module + builder + CQRS handlers. `@InjectCrudAdapter(string \| Type)`. | - -## What changed (the 5 phases) - -- **Phase 0** — Recorded baseline: `@bitwild` ecosystem was green; the copied - concepta packages had real TS compile errors. -- **Phase 1** — `rockets-app` became the self-contained superset of `common`. - Ported 7 utils + `AuthUser` (5 lines) + a **fresh** SwaggerUi module + - model interfaces. Renamed `@concepta/rockets-app` → `@concepta/rockets-app`. -- **Phase 2** — Adopted repository: merged `InjectDynamicRepository` to - `string | Type`, fixed `super.context` → `this.context` + `declare context`. -- **Phase 3** — Adopted crud + repository-typeorm: fixed the TypeORM `upsert` - typing (normalize `DeepPartial` via `repo.create()` — no cast), fixed a - union-narrowing bug that only fails under `strict:false` (distributive - conditional structural view), merged `InjectCrudAdapter`, fixed fixture - generic inference, aligned tsconfigs to exclude test files (project convention). -- **Phase 4** — Atomic cutover: - - Deleted wrappers: `rockets-common`, old `rockets-repository`, old `rockets-crud`. - - Renamed concepta folders/packages → `@concepta/*`. - - Swapped consumer imports: `rockets-common`→`rockets-app` (72 files); - upstream `@concepta/nestjs-repository`/`-typeorm`/`-crud` → `@bitwild` (93 files); - `@concepta/nestjs-common` kernel symbols split to app (52 files, 7 upstream-only - symbols kept). - - Rewired core `HookModule.forRoot({})` → `RocketsAppModule.forRoot()`. - - This resolved the silent `AppContextHost`-identity bug (the #1 risk - flagged up front). - -## Why the `@bitwild` repository now DIVERGES from upstream (important) - -The OLD `@concepta/rockets-repository` was a wrapper that re-exported upstream -`@concepta/nestjs-repository`, so they shared the **same** `AppContextHost` / -`TransactionScope` classes. The NEW `@concepta/rockets-repository` is independent -source — **different classes**. Anything that mixes the new `@bitwild` stack with -upstream `@concepta/nestjs-*` packages hits a cross-identity mismatch -("Expected AppContextHost, got object"). - -## The 4 failing e2e suites (both are documented boundaries, not bugs introduced) - -1. **`rockets-crud`: `crud.operations`, `crud.adapter`** — PRE-EXISTING - crud-internal test infra (dist-module `DataSource` DI wiring; ctx-overlay - generics in the test helper). Never passed in this repo's baseline. Source - is clean. - -2. **`rockets-server-auth`: `me-password`, `password-history`** — ARCHITECTURAL - BOUNDARY. server-auth composes `@bitwild` core (→ forces the `@bitwild` - repository for its resource layer + global `SafeCrudContextInterceptor`) - **and** upstream `@concepta/nestjs-invitation` / `nestjs-user` (→ need upstream - `TransactionScope`). One app cannot provide both repository identities. - This worked before only because the wrapper === upstream. - -## OPEN DECISION for next session (server-auth) - -Closing the server-auth boundary needs one of: - -- **(A)** Migrate the upstream auth stack (`nestjs-invitation`, `nestjs-user`, - `nestjs-otp`, `nestjs-role`, `nestjs-password`, `nestjs-federated`) to the - `@bitwild` stack. Large, separate effort — makes server-auth fully self-contained. -- **(B)** Re-introduce a repository compat layer so `@bitwild` repository stays - upstream-identity-compatible. Defeats the self-contained goal; also a "bridge" - (forbidden by AGENTS.md rule #9). -- **(C)** Accept the 2 suites as a known boundary and ship the rest. - -> You picked "keep auth on upstream" last session, but that is **structurally -> unachievable** while server-auth uses `@bitwild` core. Needs a real call between -> A / B / C. - -## How to reproduce the current state - -```bash -yarn install -yarn build # GREEN -yarn lint # PASS (4 warnings) -yarn test:e2e # 31/35 suites, 248 tests pass; 4 fail (above) -``` - -## Notes / gotchas discovered - -- Build is `tsc --build` (incremental) — it does NOT delete orphaned `dist` - outputs. After renames, `rm -rf packages/*/dist *.tsbuildinfo` before a build, - or stale `.js` files reference deleted packages at runtime. -- Tests historically transpiled with `strict:false` (now via the Vitest/SWC -pipeline); some adopted code had - bugs that only surface there (union narrowing). Fixes must pass BOTH modes. -- Adding `reflect-metadata` as a dep to `rockets-app` made yarn nest a - redundant `@nestjs/common` under `packages/rockets-app/node_modules`, which - broke a portable-type emit (`TS2742`). Reverted; the spec's `import - 'reflect-metadata'` was redundant (it's a hoisted root dep). -- Upstream `@concepta/nestjs-user`/`invitation` peer-depend on - `@concepta/nestjs-crud`/`-repository`; those deps were restored to package.json - (source uses `@bitwild`, upstream auth packages keep their upstream peer). - -## Detailed step-by-step log - -See `.context/migration-baseline.md` (gitignored) for the full Phase 0→4 record -with exact files, errors, and fixes. diff --git a/README.md b/README.md index 37f8d2a64..e89402d53 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ [![CI](https://img.shields.io/github/actions/workflow/status/conceptadev/rockets/ci-merge.yml?branch=main&label=CI)](https://github.com/conceptadev/rockets/actions/workflows/ci-merge.yml) [![Codecov](https://codecov.io/gh/conceptadev/rockets/branch/main/graph/badge.svg)](https://codecov.io/gh/conceptadev/rockets) -[![NestJS](https://img.shields.io/badge/NestJS-11-ea2845?logo=nestjs&logoColor=white)](https://nestjs.com/) +[![NestJS](https://img.shields.io/badge/NestJS-12-ea2845?logo=nestjs&logoColor=white)](https://nestjs.com/) [![TypeScript](https://img.shields.io/badge/TypeScript-5.0+-3178c6?logo=typescript&logoColor=white)](https://www.typescriptlang.org/) [![License](https://img.shields.io/badge/license-BSD--3--Clause-green.svg)](LICENSE.txt) @@ -13,9 +13,9 @@ **Status:** pre-1.0 (`0.0.1-dev.0`, on npm under `@concepta/*` with dist-tag `alpha`). The public surface (`AuthAdapterInterface`, `defineResource`, -`defineModuleResource`, `RepositoryInterface`, the `RocketsModule.forRoot` -options shape) is stable; field renames are still possible before 1.0. Pin exact -versions in production. +`defineModuleResource`, `RepositoryInterface`, `createServer`) is being +prepared for 1.0; breaking refinements are still possible until that release. +Pin exact versions in production. ## Table of contents @@ -257,7 +257,7 @@ access-control rules. Rockets does not pretend to write those for you. ### Prerequisites -- Node 18+. +- Node 20+ (required by NestJS 12). - A package manager (yarn 4 / npm / pnpm — examples below use yarn). - A database adapter — TypeORM with any supported driver is the most common. Firestore works via `@concepta/rockets-repository-firestore`. @@ -307,7 +307,7 @@ yarn add @concepta/rockets@alpha \ | Pulled in for you | Packages | | ---------------------- | ---------------------------------------------------------------------------------------------------------------------- | -| Other `@concepta/*` | `rockets-core`, `rockets-repository-typeorm` | +| Other `@concepta/*` | `rockets-core` | | Upstream motor | `@concepta/nestjs-{core,repository,crud,authentication,access-control}` (via `@concepta/rockets-core` re-exports) | | Nest (Rockets runtime) | `@nestjs/common`, `@nestjs/core`, `@nestjs/cqrs`, `@nestjs/swagger`, `@nestjs/config` | @@ -342,6 +342,7 @@ import { AuthAdapterInterface, AuthAttemptResult, AuthRequest, + defineAuthAdapter, extractBearerToken, } from '@concepta/rockets'; @@ -364,6 +365,8 @@ export class JwtAdapter implements AuthAdapterInterface { } } } + +export const jwtAuth = defineAuthAdapter(JwtAdapter); ``` Declare a resource — this is the entire CRUD definition: @@ -381,42 +384,24 @@ export class PetEntity { } ``` -Add a small TypeORM bootstrap helper in your app — the adapter is -`@concepta/rockets-repository-typeorm`, but the connection-options wrapper stays -app-local so core never takes a TypeORM dependency. It implements -`RepositoryBootstrap` so the planner calls `forRoot(entities)` once from -`resources[]` + `userMetadata`, without a hand-maintained entity list: +Create the TypeORM bootstrap at the boundary. The adapter owns the wrapper, so +applications do not copy infrastructure helpers: ```typescript -// src/repository/define-typeorm-repository.ts -import type { DynamicModule, PlainLiteralObject, Type } from '@nestjs/common'; -import { TypeOrmModule, TypeOrmModuleOptions } from '@nestjs/typeorm'; -import { TypeOrmRepositoryModule } from '@concepta/rockets-repository-typeorm'; -import type { RepositoryBootstrap } from '@concepta/rockets-core'; -import type { - DynamicRepositoryModule, - RepositoryProviderOptions, -} from '@concepta/nestjs-repository'; - -export function defineTypeOrmRepository< - Connection extends TypeOrmModuleOptions, ->(connection: Connection): RepositoryBootstrap { - return { - name: 'typeorm-bootstrap', - forFeature(entities: RepositoryProviderOptions[]): DynamicRepositoryModule { - return TypeOrmRepositoryModule.forFeature(entities); - }, - forRoot(entities: ReadonlyArray>): DynamicModule { - return TypeOrmModule.forRoot({ ...connection, entities: [...entities] }); - }, - }; -} +import { defineTypeOrmRepository } from '@concepta/rockets-repository-typeorm'; + +const repository = defineTypeOrmRepository({ + type: 'sqlite', + database: ':memory:', + synchronize: true, + dropSchema: true, +}); ``` **Why this exists:** you pass only connection options (`type`, `database`, `synchronize`, …). You never maintain `entities: [PetEntity, UserMetadataEntity, …]` on `TypeOrmModule.forRoot`. When -`RocketsModule` boots, the registration planner walks `resources[]`, +the server boots, the registration planner walks `resources[]`, `userMetadata.entity`, and any entities contributed by auth integrations, then calls `forRoot(mergedEntities)` once and `forFeature` per table. Services use `@InjectDynamicRepository(PetEntity)` and get a `RepositoryInterface` @@ -425,41 +410,41 @@ calls `forRoot(mergedEntities)` once and `forFeature` per table. Services use Compose the app: ```typescript -// src/app.module.ts -import { Module } from '@nestjs/common'; -import { RocketsModule, defineResource } from '@concepta/rockets'; +// src/server.ts +import { NestFactory } from '@nestjs/core'; +import { createServer, defineResource } from '@concepta/rockets'; +import { defineTypeOrmRepository } from '@concepta/rockets-repository-typeorm'; import { OwnerStampHook, OwnerScopeHook } from '@concepta/rockets-core'; -import { JwtAdapter } from './auth/jwt.adapter'; +import { jwtAuth } from './auth/jwt.adapter'; import { PetEntity } from './pet/pet.entity'; import { UserMetadataEntity } from './user/user-metadata.entity'; import { UserMetadataCreateDto, UserMetadataUpdateDto } from './user/dto'; -import { defineTypeOrmRepository } from './repository/define-typeorm-repository'; -@Module({ - imports: [ - RocketsModule.forRoot({ - auth: JwtAdapter, - userMetadata: { - entity: UserMetadataEntity, - createDto: UserMetadataCreateDto, - updateDto: UserMetadataUpdateDto, - }, - repository: defineTypeOrmRepository({ - type: 'sqlite', - database: ':memory:', - synchronize: true, - dropSchema: true, - }), - resources: [ - defineResource({ - entity: PetEntity, - hooks: [OwnerStampHook.for(PetEntity), OwnerScopeHook.for(PetEntity)], - }), - ], +const repository = defineTypeOrmRepository({ + type: 'sqlite', + database: ':memory:', + synchronize: true, + dropSchema: true, +}); + +export const server = createServer({ + auth: jwtAuth, + userMetadata: { + entity: UserMetadataEntity, + createDto: UserMetadataCreateDto, + updateDto: UserMetadataUpdateDto, + }, + repository, + resources: [ + defineResource({ + entity: PetEntity, + hooks: [OwnerStampHook.for(PetEntity), OwnerScopeHook.for(PetEntity)], }), ], -}) -export class AppModule {} +}); + +const app = await NestFactory.create(server); +await app.listen(3000); ``` Run it: @@ -486,20 +471,15 @@ Install the same packages as above plus `@concepta/rockets-auth` and the upstrea `@concepta/nestjs-*` line (most are transitive dependencies; `yarn install` will pull them). -Compose with `defineRocketsAuth()`. Reuse the same `defineTypeOrmRepository` -helper from path A and pass the **same instance** to both -`defineRocketsAuth({ persistence: { module: repo } })` and -`RocketsModule.forRoot({ repository: repo })`. Register auth persistence rows -via `buildRocketsAuthResources()` on `resources`: +Compose with `defineRocketsAuth()`. Give it the TypeORM bootstrap once; the +integration contributes its auth rows, root repository, metadata contract, and +guard preference to the surrounding server: ```typescript import { Module } from '@nestjs/common'; -import { - defineRocketsAuth, - buildRocketsAuthResources, -} from '@concepta/rockets-auth'; +import { defineRocketsAuth } from '@concepta/rockets-auth'; import { RocketsModule } from '@concepta/rockets'; -import { defineTypeOrmRepository } from './repository/define-typeorm-repository'; +import { defineTypeOrmRepository } from '@concepta/rockets-repository-typeorm'; const repo = defineTypeOrmRepository({ type: 'sqlite', @@ -542,18 +522,11 @@ const rocketsAuthInput = { }), }; -const rocketsAuth = defineRocketsAuth(rocketsAuthInput); -const rocketsAuthResources = buildRocketsAuthResources( - rocketsAuthInput.persistence, - rocketsAuthInput.invitationEntity, -); - @Module({ imports: [ RocketsModule.forRoot({ - auth: rocketsAuth, - repository: repo, - resources: [...rocketsAuthResources /* your defineResource bundles */], + auth: defineRocketsAuth(rocketsAuthInput), + resources: [/* your application defineResource bundles */], }), ], }) @@ -575,16 +548,17 @@ The monorepo ships runnable sample apps for both paths (`yarn sample:dev` and `auth` accepts a single `AuthBootstrap` or an array. Each entry is one of: -- `defineFirebaseAuth({ forRoot | forRootAsync })` — Firebase Admin + +- `defineFirebaseAuth({ firebaseApp })` or the explicit `{ forRootAsync }` + variant — Firebase Admin + `FirebaseAuthAdapter` (`@concepta/rockets-adapter-firebase`). -- `defineRocketsAuth(...)` — built-in signup/login stack - (`@concepta/rockets-auth`); pair with `buildRocketsAuthResources()` on - `resources`. -- App-local `AuthBootstrap` — `{ adapter, forRoot? }` for custom adapters (see +- `defineRocketsAuth(...)` — complete built-in signup/login stack and its owned + persistence contributions (`@concepta/rockets-auth`). +- `defineAuthAdapter(Adapter, options?)` — complete host wiring for a custom + adapter (see `defineApiKeyAuth()` in sample-code-review). -Entity rows for auth-owned tables belong on `resources[]`, not inside the auth -helper. +Explicit server options override integration-contributed defaults. Conflicting +defaults from two integrations fail at startup instead of depending on order. ```typescript import { defineFirebaseAuth } from '@concepta/rockets-adapter-firebase'; @@ -668,11 +642,10 @@ the caller owns the parent via `PathScopeGuard`. ### Wire TypeORM without hand-registering entities -Use a small app-local `defineTypeOrmRepository` helper (full sample in **Path -A** above). It implements `RepositoryBootstrap` from `@concepta/rockets-core` -and wraps `TypeOrmRepositoryModule` from `@concepta/rockets-repository-typeorm`; -only the helper (your connection options) lives in the app, so core never takes -a TypeORM dependency. Firestore-only apps skip it and use +Import `defineTypeOrmRepository` from +`@concepta/rockets-repository-typeorm`. It implements `RepositoryBootstrap` +and keeps TypeORM connection concerns in the adapter package while core stays +storage-agnostic. Firestore-only apps skip it and use `@concepta/rockets-repository-firestore` instead. #### What you declare vs what the framework registers @@ -737,8 +710,8 @@ No `TypeOrmModule.forFeature([PetEntity])` in feature modules. No `@InjectRepository`. If the entity is in the registration plan, `@InjectDynamicRepository` resolves at runtime. -**Built-in auth (path B):** pass the **same** `repository` instance to both -entry points so one connection serves app tables and auth tables: +**Built-in auth (path B):** pass the repository to `defineRocketsAuth`; its +composition contribution makes the same connection serve app and auth tables: ```typescript const repository = defineTypeOrmRepository({ @@ -758,7 +731,6 @@ const rocketsAuth = defineRocketsAuth({ @Module({ imports: [ RocketsModule.forRoot({ - repository, auth: rocketsAuth, resources: [ /* pet resources — no per-resource persistence block */ @@ -956,20 +928,20 @@ it: one `RocketsModule.forRoot({ ... })` object is split by | `@concepta/nestjs-crud` | `@concepta/rockets-core` (re-export) | Generated controllers, CQRS commands/queries, default handlers | | `@concepta/nestjs-core`, `@concepta/nestjs-authentication` | `@concepta/rockets-core` | Hook resolution (`CoreModule`), shared exceptions, auth primitives | | `@concepta/nestjs-access-control` | opt-in `accessControl` option (import symbols from upstream) | Grant table, `AccessControlGuard`, route decorators | -| `@concepta/nestjs-repository-typeorm` | `@concepta/rockets-repository-typeorm` (thin wrapper) + app-local bootstrap | SQL adapter — `@concepta/rockets-repository-typeorm`'s main entry re-exports the upstream package verbatim; wrapped by `defineTypeOrmRepository` | +| `@concepta/nestjs-repository-typeorm` | `@concepta/rockets-repository-typeorm` | SQL adapter plus `defineTypeOrmRepository`, which supplies connection options and accepts the planner-derived entity list | | `@concepta/nestjs-user`, `role`, `otp`, `password`, `invitation`, `federated`, `email`, `event` | wired inside `@concepta/rockets-auth` | Built-in auth HTTP + persistence rows (path B only) | | Rockets layer | Role | | --------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- | | `@concepta/rockets-core` | **Planner and contracts**: `defineResource`, `buildAppRegistrationPlan`, `AuthServerGuard`, owner/path hooks, swagger registration | | `@concepta/rockets` (server) | **External-auth presentation**: `MeController`, default `APP_GUARD`, `auth` chain merge | -| `@concepta/rockets-auth` | **Built-in identity bundle**: `defineRocketsAuth()` + `buildRocketsAuthResources()` | +| `@concepta/rockets-auth` | **Built-in identity bundle**: `defineRocketsAuth()` with owned composition contributions | **Path B uses both** `@concepta/rockets` and `@concepta/rockets-auth`: -`defineRocketsAuth()` supplies the auth bootstrap; spread -`buildRocketsAuthResources()` into `resources`; -`RocketsModule.forRoot({ auth, repository, resources })` still comes from the -server package. They are sibling packages over core, not parent/child. +`defineRocketsAuth()` supplies the auth bootstrap plus its persistence, +metadata, and guard defaults; `createServer({ auth, resources })` (or the +lower-level `RocketsModule.forRoot`) still comes from the server package. They +are sibling packages over core, not parent/child. **Repository injection (upstream contract, Rockets-local decorator):** @@ -995,7 +967,7 @@ configuration façade** — not a fork. | **Core re-exports (former `@concepta/rockets-common`)** | `@concepta/rockets-common` was deleted; its helpers (`AuthUser`, `InjectDynamicRepository`, `SwaggerUiModule`, `deriveEntityKey`, …) and upstream re-exports now live inside `@concepta/rockets-core`. This is **not** a replacement for the upstream **app-module** composition pattern — that wiring still lives in Concepta; Rockets adds a **second** entry point (`RocketsModule.forRoot`) that feeds the same motors. | | **Port backlog (server path)** | On v8 today: `core`, `repository`, `crud`, `hook`, `common`, `authentication`, `access-control`. Still on v7 in this monorepo: `swagger-ui` (and `email` / `event` on the auth path) — version-mismatched intentionally and tested in CI. | | **Repo migration** | Moving all of `nestjs-modules` into this git repo is **optional** for product validation. Shipping fixes against published `@concepta/*` alphas is fine; monorepo colocation is for AI context and version lock, not a prerequisite to use Rockets. | -| **Safe to keep building on** | These are intentional, tested surfaces — not throwaway experiments: `AuthAdapterInterface.authenticate`, `RepositoryInterface` + dynamic repository keys (class **or** string token), `defineResource` / planner-driven entity registration, `defineRocketsAuth({ persistence: { module } })` sharing one `repository` instance with `RocketsModule.forRoot`. | +| **Safe to keep building on** | These are intentional, tested surfaces — not throwaway experiments: `createServer`, `AuthAdapterInterface.authenticate`, `RepositoryInterface` + dynamic repository keys (class **or** string token), `defineResource` / planner-driven entity registration, and complete `defineRocketsAuth({ persistence })` contributions. | **Custom validation / business rules:** use `defineHook` from `@concepta/rockets-core` for simple entity lifecycle rules, upstream @@ -1008,12 +980,12 @@ to 4xx — a bare `Error` in a hook often surfaces as 500. | Package | npm name | Purpose | Docs | Status | | --------------------------------------- | --------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------- | ------- | -| `packages/rockets-core` | `@concepta/rockets-core` | Composition planner. Auth chain, `buildAppRegistrationPlan`, `defineResource` / `defineModuleResource` / `defineSubResource`, `defineHook`, owner/path hooks, swagger registration, shared helpers, zod layer at `@concepta/rockets-core/zod`, opt-in `accessControl`. | [README](packages/rockets-core/README.md) | stable | -| `packages/rockets-repository-typeorm` | `@concepta/rockets-repository-typeorm` | TypeORM adapter for the dynamic repository contract — a thin wrapper whose main entry re-exports upstream `@concepta/nestjs-repository-typeorm` verbatim, plus the zod `SchemaEntityCompiler` at `@concepta/rockets-repository-typeorm/zod`. | [README](packages/rockets-repository-typeorm/README.md) | stable | +| `packages/rockets-core` | `@concepta/rockets-core` | Composition planner. Auth chain, `buildAppRegistrationPlan`, `defineResource` / `defineModuleResource` / `defineSubResource`, `defineHook`, owner/path hooks, swagger registration, shared helpers, zod layer at `@concepta/rockets-core/zod`, opt-in `accessControl`. | [README](packages/rockets-core/README.md) | preview | +| `packages/rockets-repository-typeorm` | `@concepta/rockets-repository-typeorm` | TypeORM adapter and `defineTypeOrmRepository` bootstrap for planner-derived entity registration, plus the zod `SchemaEntityCompiler` at `@concepta/rockets-repository-typeorm/zod`. | [README](packages/rockets-repository-typeorm/README.md) | preview | | `packages/rockets-repository-firestore` | `@concepta/rockets-repository-firestore` | Firestore adapter implementing `RepositoryAdapter`. Per-entity opt-in. | [README](packages/rockets-repository-firestore/README.md) | preview | | `packages/rockets-adapter-firebase` | `@concepta/rockets-adapter-firebase` | Firebase Auth adapter implementing `AuthAdapterInterface`. | [README](packages/rockets-adapter-firebase/README.md) | preview | -| `packages/rockets-server` | `@concepta/rockets` | External-auth presentation layer. `MeController`, `APP_GUARD` opt-in, `auth` chain. | [README](packages/rockets-server/README.md) | stable | -| `packages/rockets-server-auth` | `@concepta/rockets-auth` | Built-in auth: signup, login, OTP, recovery, invitations, roles, admin user CRUD. `defineRocketsAuth()`. | [README](packages/rockets-server-auth/README.md) | alpha | +| `packages/rockets-server` | `@concepta/rockets` | Launch-facing `createServer`, external-auth presentation, optional `/me`, default guard, and auth chain. | [README](packages/rockets-server/README.md) | preview | +| `packages/rockets-server-auth` | `@concepta/rockets-auth` | Built-in auth: signup, login, recovery, OTP, invitations, roles, throttling, and admin user CRUD. | [README](packages/rockets-server-auth/README.md) | preview | ### Repository layout @@ -1044,13 +1016,14 @@ rockets/ `testing`); satellite packages (`cqrs`, `typeorm`, `jwt`, `passport`, `config`, `throttler`) remain on their current stable majors until a Nest 12 line is published. -- **Node**: `>=18.0.0`. +- **Node**: `>=20.0.0` (the minimum supported by NestJS 12). ### Common scripts (from the monorepo root) | Command | Purpose | | ----------------------------- | ---------------------------------------------------------------------------------------------------------- | -| `yarn publish:conceptadev` | Build + publish all `@concepta/*` packages to npm (`--tag alpha`). | +| `yarn release:check` | Run the complete build, package, type, lint, unit, e2e, and sample release gate. | +| `yarn release:dry` | Build publish archives for every public `@concepta/*` workspace without publishing. | | `yarn install && yarn build` | Bootstrap + compile every local `@concepta/*` package. | | `yarn test` | Unit tests (Vitest). | | `yarn typecheck:spec` | Type-checks test files — the runner only transpiles them. | @@ -1060,7 +1033,6 @@ rockets/ | `yarn sample:dev` | Run `sample-server` in watch mode. | | `yarn sample-auth:dev` | Run `sample-server-auth` in watch mode. | | `yarn sample-code-review:dev` | Build + run the full-stack example. | -| `yarn generate-swagger` | Dump the OpenAPI spec from `sample-server-auth`. | --- diff --git a/examples/sample-code-review/README.md b/examples/sample-code-review/README.md index 27973cd04..32c69f604 100644 --- a/examples/sample-code-review/README.md +++ b/examples/sample-code-review/README.md @@ -75,7 +75,7 @@ chain so server-to-server callers can authenticate with `X-Api-Key`. ### Prerequisites -- Node 18+, Yarn 4. +- Node 20+, Yarn 4. - A Firebase project for real auth (or `FIREBASE_USE_FAKE=true` for in-process verification). - A GitHub OAuth App if you want the GitHub connect flow to work. @@ -227,7 +227,7 @@ examples/sample-code-review │ │ ├── auth-firebase/ defineFirebaseAuth wiring │ │ ├── auth-api-key/ Second AuthBootstrap in the chain — ApiKeyEntity + POST /api-keys (mint) + adapter │ │ ├── github/ GitHub OAuth + repo browse -│ │ ├── repository/ defineTypeOrmRepository + Firestore persistence helpers +│ │ ├── repository/ Firestore persistence helper │ │ ├── config/ GithubConfig / OpenaiConfig (env-var readers) │ │ ├── zod-bindings.ts bindZodResources(typeOrmZodEntityCompiler) │ │ ├── user-metadata.schema.ts zod schema -> { entity, createDto, updateDto, responseDto } diff --git a/examples/sample-code-review/apps/api/package.json b/examples/sample-code-review/apps/api/package.json index 5ac77ea54..1177bad0b 100644 --- a/examples/sample-code-review/apps/api/package.json +++ b/examples/sample-code-review/apps/api/package.json @@ -43,7 +43,7 @@ "@nestjs/cli": "12.0.0-alpha.6", "@nestjs/testing": "12.0.0-alpha.5", "@swc/core": "^1.15.47", - "@types/node": "^18.19.44", + "@types/node": "^20.19.0", "@types/supertest": "^6.0.2", "supertest": "^6.3.4", "ts-node": "^10.9.2", diff --git a/examples/sample-code-review/apps/api/src/app.module.ts b/examples/sample-code-review/apps/api/src/app.module.ts index 402d0c5e6..f207213ca 100644 --- a/examples/sample-code-review/apps/api/src/app.module.ts +++ b/examples/sample-code-review/apps/api/src/app.module.ts @@ -1,6 +1,7 @@ import { Module } from '@nestjs/common'; import { ConfigModule } from '@nestjs/config'; import { RocketsModule } from '@concepta/rockets'; +import { defineTypeOrmRepository } from '@concepta/rockets-repository-typeorm'; import { defineFirebaseAuth } from '@concepta/rockets-adapter-firebase'; import { defineModuleResource } from '@concepta/rockets-core'; import { createFirebaseAdminApp } from './auth-firebase'; @@ -13,7 +14,6 @@ import { UserMetadataCreateDto, UserMetadataUpdateDto, } from './user-metadata.schema'; -import { defineTypeOrmRepository } from './repository/define-typeorm-repository'; import { githubFeature } from './github'; import { analysisFeature } from './analysis'; diff --git a/examples/sample-code-review/apps/api/src/auth-api-key/define-api-key-auth.ts b/examples/sample-code-review/apps/api/src/auth-api-key/define-api-key-auth.ts index 6188de25d..c05fc54c1 100644 --- a/examples/sample-code-review/apps/api/src/auth-api-key/define-api-key-auth.ts +++ b/examples/sample-code-review/apps/api/src/auth-api-key/define-api-key-auth.ts @@ -1,4 +1,4 @@ -import { defineModuleResource } from '@concepta/rockets-core'; +import { defineAuthAdapter, defineModuleResource } from '@concepta/rockets-core'; import type { AuthBootstrap } from '@concepta/rockets-core'; import { ApiKeyAuthAdapter } from './api-key.adapter'; import { ApiKeyController } from './api-key.controller'; @@ -12,13 +12,7 @@ export const apiKeyAuthResource = defineModuleResource({ * API key auth chain entry. Pair with `apiKeyAuthResource` in `resources[]`. */ export function defineApiKeyAuth(): AuthBootstrap { - return { - adapter: ApiKeyAuthAdapter, - forRoot: () => ({ - module: class ApiKeyAuthHostModule {}, - providers: [ApiKeyAuthAdapter], - controllers: [ApiKeyController], - exports: [ApiKeyAuthAdapter], - }), - }; + return defineAuthAdapter(ApiKeyAuthAdapter, { + controllers: [ApiKeyController], + }); } diff --git a/examples/sample-code-review/apps/api/src/repository/define-typeorm-repository.ts b/examples/sample-code-review/apps/api/src/repository/define-typeorm-repository.ts deleted file mode 100644 index 38fed55b7..000000000 --- a/examples/sample-code-review/apps/api/src/repository/define-typeorm-repository.ts +++ /dev/null @@ -1,27 +0,0 @@ -import type { DynamicModule, PlainLiteralObject, Type } from '@nestjs/common'; -import { TypeOrmModule, TypeOrmModuleOptions } from '@nestjs/typeorm'; -import { TypeOrmRepositoryModule } from '@concepta/rockets-repository-typeorm'; -import type { RepositoryBootstrap } from '@concepta/rockets-core'; -import type { - DynamicRepositoryModule, - RepositoryProviderOptions, -} from '@concepta/nestjs-repository'; - -export function defineTypeOrmRepository( - connection: Connection, -): RepositoryBootstrap { - return { - name: 'typeorm-bootstrap', - forFeature(entities: RepositoryProviderOptions[]): DynamicRepositoryModule { - return TypeOrmRepositoryModule.forFeature(entities); - }, - forRoot( - entities: ReadonlyArray>, - ): DynamicModule { - return TypeOrmModule.forRoot({ - ...connection, - entities: [...entities], - }); - }, - }; -} diff --git a/examples/sample-server-auth/README.md b/examples/sample-server-auth/README.md index 6640f9ed8..66b89e4c8 100644 --- a/examples/sample-server-auth/README.md +++ b/examples/sample-server-auth/README.md @@ -169,7 +169,6 @@ examples/sample-server-auth │ ├── app.acl.ts Role + resource enums + accesscontrol grants │ ├── access-control.service.ts AccessControlServiceInterface impl │ ├── main.ts Bootstrap (helmet, validation, swagger) -│ ├── repository/ defineTypeOrmRepository bootstrap (shared with defineRocketsAuth) │ ├── shared/persistence/ AuditedSqliteEntity (app-owned audit columns) │ ├── modules/ │ │ ├── user/ UserEntity + credential / otp / role-link entities + DTOs @@ -189,7 +188,8 @@ examples/sample-server-auth | `GET /me`, `PATCH /me` | From `@concepta/rockets`. | | `PATCH /me/password` | Password change. | | `POST /otp`, `PATCH /otp` | OTP issue / verify. | -| `POST /recovery/*` | Password recovery flow (wired to notification handlers). | +| `POST /recovery/login`, `POST /recovery/password` | Enumeration-safe recovery initiation. | +| `GET /recovery/passcode/:passcode`, `PATCH /recovery/password` | Passcode validation and password reset. | | `/admin/users`, `/admin/users/:userId/roles` | Admin user CRUD + role assignment. | | `/admin/roles` | Role CRUD (`roleCrud` config). | | `/admin/invitations`, `/invitation-acceptance`, … | Invitation flow. | @@ -206,11 +206,11 @@ examples/sample-server-auth ### Persistence wiring -A single `defineTypeOrmRepository({...})` bootstrap is shared by -`RocketsModule.forRoot({ repository })` and `defineRocketsAuth({ -persistence: { module } })`. The planner derives the full entity list -from `resources[]`, `userMetadata.entity`, and the auth -`persistence.entities` map — there is no top-level +A single `defineTypeOrmRepository({...})` bootstrap is passed to +`defineRocketsAuth({ persistence: { module } })`. That integration contributes +the root repository, auth resources, and metadata contract to +`RocketsModule`. The planner derives the full entity list from app resources +and the auth `persistence.entities` map — there is no top-level `TypeOrmModule.forRoot({ entities: [...] })` to keep in sync. Pet resources omit per-resource `persistence` so they inherit the root adapter. diff --git a/examples/sample-server-auth/package.json b/examples/sample-server-auth/package.json index 3cb358205..4c50d2353 100644 --- a/examples/sample-server-auth/package.json +++ b/examples/sample-server-auth/package.json @@ -46,7 +46,7 @@ "@nestjs/testing": "12.0.0-alpha.5", "@swc/core": "^1.15.47", "@types/jsonwebtoken": "^9.0.3", - "@types/node": "^18.19.44", + "@types/node": "^20.19.0", "@types/supertest": "^6.0.2", "supertest": "^6.3.4", "ts-node": "^10.9.2", diff --git a/examples/sample-server-auth/src/app.module.ts b/examples/sample-server-auth/src/app.module.ts index 72db99bcb..4ede232b5 100644 --- a/examples/sample-server-auth/src/app.module.ts +++ b/examples/sample-server-auth/src/app.module.ts @@ -1,12 +1,12 @@ import { Global, Logger, Module } from '@nestjs/common'; import { EventModule } from '@concepta/nestjs-event'; import { - buildRocketsAuthResources, defineRocketsAuth, type DefineRocketsAuthInput, type EmailSendOptionsInterface, } from '@concepta/rockets-auth'; import { RocketsModule } from '@concepta/rockets'; +import { defineTypeOrmRepository } from '@concepta/rockets-repository-typeorm'; import { ACService } from './access-control.service'; import { acRules } from './app.acl'; @@ -42,9 +42,8 @@ import { } from './modules/user'; import { RoleEntity, RoleDto, RoleUpdateDto } from './modules/role'; import { RoleCreateDto } from './modules/role/role.dto'; -import { defineTypeOrmRepository } from './repository/define-typeorm-repository'; -// Single TypeORM bootstrap shared by every persistence consumer below. +// Single TypeORM bootstrap owned by the auth integration below. // `defineTypeOrmRepository` returns a `RepositoryBootstrap`, which the // planner uses for both: // - `forRoot(planEntities)` — DB connection + the union of every @@ -52,9 +51,8 @@ import { defineTypeOrmRepository } from './repository/define-typeorm-repository' // `defineRocketsAuth({ persistence })`. // - `forFeature(entities)` — one `DYNAMIC_REPOSITORY_TOKEN_` // provider per registered entity. -// Reference equality matters: pass the SAME `repo` instance everywhere, -// otherwise the planner splits the entity list across two adapters and -// `TypeOrmModule.forRoot` boots with an incomplete entity set. +// `defineRocketsAuth` contributes this same bootstrap to Rockets, so the host +// does not need to repeat it in `RocketsModule.forRoot`. const repo = defineTypeOrmRepository({ type: 'sqlite', database: ':memory:', @@ -171,10 +169,6 @@ const rocketsAuthInput: DefineRocketsAuthInput = { }; const rocketsAuth = defineRocketsAuth(rocketsAuthInput); -const rocketsAuthResources = buildRocketsAuthResources( - rocketsAuthInput.persistence, - rocketsAuthInput.invitationEntity, -); @Global() @Module({ @@ -183,11 +177,7 @@ const rocketsAuthResources = buildRocketsAuthResources( PetModule, RocketsModule.forRoot({ auth: rocketsAuth, - userMetadata: rocketsAuthInput.userMetadata, - enableGlobalGuard: false, - repository: repo, resources: [ - ...rocketsAuthResources, createPetResource(), createPetVaccinationResource(), createPetAppointmentResource(), diff --git a/examples/sample-server-auth/src/repository/define-typeorm-repository.ts b/examples/sample-server-auth/src/repository/define-typeorm-repository.ts deleted file mode 100644 index 4cbb1cc9c..000000000 --- a/examples/sample-server-auth/src/repository/define-typeorm-repository.ts +++ /dev/null @@ -1,47 +0,0 @@ -import type { DynamicModule, PlainLiteralObject, Type } from '@nestjs/common'; -import { TypeOrmModule, TypeOrmModuleOptions } from '@nestjs/typeorm'; -import { TypeOrmRepositoryModule } from '@concepta/rockets-repository-typeorm'; -import type { RepositoryBootstrap } from '@concepta/rockets-core'; -import type { - DynamicRepositoryModule, - RepositoryProviderOptions, -} from '@concepta/nestjs-repository'; - -/** - * Returns a `RepositoryBootstrap` that: - * - Forwards `forFeature(entities)` to upstream `TypeOrmRepositoryModule`. - * - Implements `forRoot(entities)` by wrapping `TypeOrmModule.forRoot` - * with the connection options the caller passed in plus the entity set - * the Rockets planner derived from `resources[]`, `userMetadata`, and - * `defineRocketsAuth({ persistence })`. - * - * Pass the SAME instance to every persistence consumer in the app: - * - `RocketsModule.forRoot({ repository: repo })` (root adapter) - * - `defineRocketsAuth({ persistence: { module: repo } })` - * - * The planner uses reference equality to group entities per adapter and - * to decide whether to call `forRoot`. Splitting `repo` into two - * distinct objects would split the entity list and break the connection. - * - * Identical to the helper in `examples/sample-server` — duplicated here so - * each sample app boots without depending on the other's source tree. - * - */ -export function defineTypeOrmRepository< - Connection extends TypeOrmModuleOptions, ->(connection: Connection): RepositoryBootstrap { - return { - name: 'typeorm-bootstrap', - - forFeature(entities: RepositoryProviderOptions[]): DynamicRepositoryModule { - return TypeOrmRepositoryModule.forFeature(entities); - }, - - forRoot(entities: ReadonlyArray>): DynamicModule { - return TypeOrmModule.forRoot({ - ...connection, - entities: [...entities], - }); - }, - }; -} diff --git a/examples/sample-server/README.md b/examples/sample-server/README.md index d01a3c961..4d12fc3e4 100644 --- a/examples/sample-server/README.md +++ b/examples/sample-server/README.md @@ -139,7 +139,6 @@ Handwritten entity + DTO path still demonstrated in `pet-vaccination/` for compa examples/sample-server ├── src/ │ ├── auth/ AuthBootstrap + JWT signup/login -│ ├── repository/ defineTypeOrmRepository bootstrap │ ├── zod-bindings.ts bindZodResources(typeOrmZodEntityCompiler) │ ├── user-metadata.schema.ts zod schema -> { entity, createDto, updateDto, responseDto } │ ├── resources/ CRUD + sub-resource + module bundles diff --git a/examples/sample-server/package.json b/examples/sample-server/package.json index c6a6ce8fd..e51893cad 100644 --- a/examples/sample-server/package.json +++ b/examples/sample-server/package.json @@ -39,7 +39,7 @@ "@nestjs/testing": "12.0.0-alpha.5", "@swc/core": "^1.15.47", "@types/jsonwebtoken": "^9.0.3", - "@types/node": "^18.19.44", + "@types/node": "^20.19.0", "@types/supertest": "^6.0.2", "supertest": "^6.3.4", "ts-loader": "^9.5.1", diff --git a/examples/sample-server/src/app.module.ts b/examples/sample-server/src/app.module.ts index 494fb5d90..6b907411f 100644 --- a/examples/sample-server/src/app.module.ts +++ b/examples/sample-server/src/app.module.ts @@ -1,8 +1,8 @@ import { Module } from '@nestjs/common'; -import { RocketsModule } from '@concepta/rockets'; +import { createServer } from '@concepta/rockets'; +import { defineTypeOrmRepository } from '@concepta/rockets-repository-typeorm'; import { userMetadataConfig } from './user-metadata.schema'; import { defineSampleAuth, sampleAuthUserResource } from './auth'; -import { defineTypeOrmRepository } from './repository/define-typeorm-repository'; import { petResource } from './resources/pet'; import { petVaccinationResource } from './resources/pet-vaccination'; // `/tags` is fully zod-driven (nestjs-zod DTOs + generated entity from @@ -26,33 +26,33 @@ import { adminFeature } from './admin'; import { auditFeature } from './audit'; import { eventsFeature } from './events'; -@Module({ - imports: [ - RocketsModule.forRoot({ - auth: defineSampleAuth(), - userMetadata: userMetadataConfig, - repository: defineTypeOrmRepository({ - type: 'sqlite', - database: ':memory:', - synchronize: true, - dropSchema: true, - }), - resources: [ - sampleAuthUserResource, - petResource, - petVaccinationResource, - tagZodResource, - authorZodResource, - bookZodResource, - appointmentResource, - reminderZodResource, - petShareFeature, - petTransferFeature, - adminFeature, - auditFeature, - eventsFeature, - ], - }), +export const server = createServer({ + auth: defineSampleAuth(), + userMetadata: userMetadataConfig, + repository: defineTypeOrmRepository({ + type: 'sqlite', + database: ':memory:', + synchronize: true, + dropSchema: true, + }), + resources: [ + sampleAuthUserResource, + petResource, + petVaccinationResource, + tagZodResource, + authorZodResource, + bookZodResource, + appointmentResource, + reminderZodResource, + petShareFeature, + petTransferFeature, + adminFeature, + auditFeature, + eventsFeature, ], +}); + +@Module({ + imports: [server], }) export class AppModule {} diff --git a/examples/sample-server/src/auth/define-sample-auth.ts b/examples/sample-server/src/auth/define-sample-auth.ts index f8f5bc18c..25921639d 100644 --- a/examples/sample-server/src/auth/define-sample-auth.ts +++ b/examples/sample-server/src/auth/define-sample-auth.ts @@ -1,5 +1,5 @@ import type { AuthBootstrap } from '@concepta/rockets-core'; -import { defineModuleResource } from '@concepta/rockets-core'; +import { defineAuthAdapter, defineModuleResource } from '@concepta/rockets-core'; import { UserEntity } from './user.entity'; import { AuthController } from './auth.controller'; import { SampleAuthAdapter } from './auth.adapter'; @@ -14,13 +14,7 @@ export const sampleAuthUserResource = defineModuleResource({ * `RocketsModule.forRoot({ resources: [...] })`. */ export function defineSampleAuth(): AuthBootstrap { - return { - adapter: SampleAuthAdapter, - forRoot: () => ({ - module: class SampleAuthHostModule {}, - providers: [SampleAuthAdapter], - controllers: [AuthController], - exports: [SampleAuthAdapter], - }), - }; + return defineAuthAdapter(SampleAuthAdapter, { + controllers: [AuthController], + }); } diff --git a/examples/sample-server/src/main.ts b/examples/sample-server/src/main.ts index 82347c744..21d6c3973 100644 --- a/examples/sample-server/src/main.ts +++ b/examples/sample-server/src/main.ts @@ -3,7 +3,7 @@ import { HttpAdapterHost, NestFactory } from '@nestjs/core'; import { StandardSchemaValidationPipe, ValidationPipe } from '@nestjs/common'; import { SwaggerModule } from '@nestjs/swagger'; import { cleanupOpenApiDoc } from 'nestjs-zod'; -import { AppModule } from './app.module'; +import { server } from './app.module'; import { ExceptionsFilter } from '@concepta/rockets'; import helmet from 'helmet'; @@ -12,7 +12,7 @@ import { patchMePatchOpenApi } from './swagger/patch-me-openapi'; import { SwaggerUiService } from '@concepta/rockets-core'; async function bootstrap() { - const app = await NestFactory.create(AppModule); + const app = await NestFactory.create(server); app.use(helmet()); app.enableCors({ diff --git a/examples/sample-server/src/repository/define-typeorm-repository.ts b/examples/sample-server/src/repository/define-typeorm-repository.ts deleted file mode 100644 index 196818d75..000000000 --- a/examples/sample-server/src/repository/define-typeorm-repository.ts +++ /dev/null @@ -1,46 +0,0 @@ -import type { DynamicModule, PlainLiteralObject, Type } from '@nestjs/common'; -import { TypeOrmModule, TypeOrmModuleOptions } from '@nestjs/typeorm'; -import { TypeOrmRepositoryModule } from '@concepta/rockets-repository-typeorm'; -import type { RepositoryBootstrap } from '@concepta/rockets-core'; -import type { - DynamicRepositoryModule, - RepositoryProviderOptions, -} from '@concepta/nestjs-repository'; - -/** - * Returns a `RepositoryBootstrap` that: - * - Forwards `forFeature(entities)` to upstream `TypeOrmRepositoryModule`. - * - Implements `forRoot(entities)` by wrapping `TypeOrmModule.forRoot` - * with the connection options the user passed in plus the entity set - * the Rockets registration plan derived from `resources[]` and - * `userMetadata`. - * - * The user passes one factory call to `RocketsModule.forRoot(...)`; the - * connection and the per-entity registration come out of a single - * source of truth. Any `entities` key on the supplied connection is - * overridden with the registration plan's entity list. - * - * The `Connection` generic carries the concrete union member (e.g. - * `SqliteConnectionOptions`) chosen at the callsite, preserving - * driver-specific discrimination through the spread. - */ -export function defineTypeOrmRepository( - connection: Connection, -): RepositoryBootstrap { - return { - name: 'typeorm-bootstrap', - - forFeature(entities: RepositoryProviderOptions[]): DynamicRepositoryModule { - return TypeOrmRepositoryModule.forFeature(entities); - }, - - forRoot( - entities: ReadonlyArray>, - ): DynamicModule { - return TypeOrmModule.forRoot({ - ...connection, - entities: [...entities], - }); - }, - }; -} diff --git a/examples/sample-server/test/zod-full-coverage.e2e-spec.ts b/examples/sample-server/test/zod-full-coverage.e2e-spec.ts index 958b0cb50..5ed5e63d2 100644 --- a/examples/sample-server/test/zod-full-coverage.e2e-spec.ts +++ b/examples/sample-server/test/zod-full-coverage.e2e-spec.ts @@ -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, diff --git a/examples/sample-server/test/zod-parity.e2e-spec.ts b/examples/sample-server/test/zod-parity.e2e-spec.ts index 51b731477..e879c0a94 100644 --- a/examples/sample-server/test/zod-parity.e2e-spec.ts +++ b/examples/sample-server/test/zod-parity.e2e-spec.ts @@ -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, diff --git a/examples/sample-server/test/zod-swagger-golden.e2e-spec.ts b/examples/sample-server/test/zod-swagger-golden.e2e-spec.ts index 7e6995356..25ba6ccd3 100644 --- a/examples/sample-server/test/zod-swagger-golden.e2e-spec.ts +++ b/examples/sample-server/test/zod-swagger-golden.e2e-spec.ts @@ -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, diff --git a/package.json b/package.json index aa0321827..9958b7141 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,9 @@ "version": "0.0.1-dev.0", "license": "BSD-3-Clause", "private": true, + "engines": { + "node": ">=20.0.0" + }, "workspaces": { "packages": [ "packages/*", @@ -66,7 +69,7 @@ "@nestjs/testing": "12.0.0-alpha.5", "@swc/core": "^1.15.47", "@types/express": "^4.17.21", - "@types/node": "^18.19.44", + "@types/node": "^20.19.0", "@types/nodemailer": "^6.4.15", "@types/supertest": "^6.0.3", "@typescript-eslint/eslint-plugin": "^5.62.0", @@ -124,7 +127,6 @@ "test:all": "vitest run", "doc": "rimraf ./docs && typedoc", "doc:cov": "yarn doc --coverageOutputType all", - "generate-swagger": "cd packages/rockets-server-auth && yarn generate-swagger", "sample:start": "yarn workspace sample-server start:dev", "sample:go": "yarn build && yarn workspace sample-server start:dev", "sample:once": "yarn workspace sample-server start", @@ -140,14 +142,15 @@ "sample-code-review:dev": "yarn build && yarn workspace sample-code-review dev", "sample-code-review:build": "yarn build && yarn workspace sample-code-review build", "sample-code-review:test:e2e": "yarn build && yarn workspace sample-code-review test:e2e", - "samples:build": "yarn sample:build && yarn sample-auth:build", - "samples:test:e2e": "yarn sample:test:e2e && yarn sample-auth:test:e2e", + "samples:build": "yarn sample:build && yarn sample-auth:build && yarn workspace sample-code-review build", + "samples:test:e2e": "yarn sample:test:e2e && yarn sample-auth:test:e2e && yarn workspace sample-code-review test:e2e", "codacy:analyze": ".codacy/cli.sh analyze", "codacy:analyze:fix": ".codacy/cli.sh analyze --fix", "codacy:analyze:file": ".codacy/cli.sh analyze", "codacy:analyze:security": ".codacy/cli.sh analyze -t trivy", "typecheck:spec": "tsc --noEmit -p tsconfig.spec.json && yarn workspace @concepta/rockets-core test:typetests", - "release:check": "node -e \"const v=process.env.npm_config_user_agent||'';if(!/yarn\\\\/4/.test(v)){console.error('\\\\n Release commands require Yarn 4. Run them via: corepack yarn