feat(conformance): implement ReadOnly brand and reader generator
- Add ReadOnly<F> phantom brand to core-shared/conformance (compile-time enforcement that readers only wrap non-mutating use cases) - Add isReadOnly runtime predicate for boot-time assertReaderPurity - Scaffold pnpm turbo gen reader: creates integrations/readers/ with interface, implementation, test, barrel, and adds ./reader export subpath to package.json
This commit is contained in:
3
turbo/generators/templates/reader/index.ts.hbs
Normal file
3
turbo/generators/templates/reader/index.ts.hbs
Normal file
@@ -0,0 +1,3 @@
|
||||
// packages/{{kebabCase feature}}/src/integrations/readers/index.ts
|
||||
// Public surface: type-only export. Implementation is internal.
|
||||
export type { I{{pascalCase feature}}Reader } from "./{{kebabCase feature}}.reader.interface";
|
||||
14
turbo/generators/templates/reader/reader.interface.ts.hbs
Normal file
14
turbo/generators/templates/reader/reader.interface.ts.hbs
Normal file
@@ -0,0 +1,14 @@
|
||||
// packages/{{kebabCase feature}}/src/integrations/readers/{{kebabCase feature}}.reader.interface.ts
|
||||
|
||||
/**
|
||||
* Cross-feature read-only query contract for the {{kebabCase feature}} vertical.
|
||||
* Consumers import this type from `@repo/{{kebabCase feature}}/reader`.
|
||||
* Implementation is private — constructed by the feature's binder.
|
||||
*
|
||||
* Rules: Q0 (cross-feature domain queries only), Q1 (interface public,
|
||||
* impl private), Q2 (read-only — wraps only mutates:false use cases).
|
||||
*/
|
||||
export interface I{{pascalCase feature}}Reader {
|
||||
// Add methods as consumers need them. Each method must delegate to a
|
||||
// use case declared mutates: false in feature.manifest.ts.
|
||||
}
|
||||
10
turbo/generators/templates/reader/reader.test.ts.hbs
Normal file
10
turbo/generators/templates/reader/reader.test.ts.hbs
Normal file
@@ -0,0 +1,10 @@
|
||||
// packages/{{kebabCase feature}}/src/integrations/readers/{{kebabCase feature}}.reader.test.ts
|
||||
import { describe, it, expect } from "vitest";
|
||||
import { {{pascalCase feature}}Reader } from "@/integrations/readers/{{kebabCase feature}}.reader";
|
||||
|
||||
describe("{{pascalCase feature}}Reader", () => {
|
||||
it("can be constructed", () => {
|
||||
const reader = new {{pascalCase feature}}Reader();
|
||||
expect(reader).toBeDefined();
|
||||
});
|
||||
});
|
||||
25
turbo/generators/templates/reader/reader.ts.hbs
Normal file
25
turbo/generators/templates/reader/reader.ts.hbs
Normal file
@@ -0,0 +1,25 @@
|
||||
// packages/{{kebabCase feature}}/src/integrations/readers/{{kebabCase feature}}.reader.ts
|
||||
|
||||
import type { I{{pascalCase feature}}Reader } from "./{{kebabCase feature}}.reader.interface";
|
||||
|
||||
/**
|
||||
* Internal implementation of I{{pascalCase feature}}Reader.
|
||||
* Wraps existing use cases — does not add domain logic.
|
||||
* Constructed by bind-production / bind-dev-seed; never exported.
|
||||
*
|
||||
* All injected use cases MUST be ReadOnly-branded (mutates: false).
|
||||
*/
|
||||
export class {{pascalCase feature}}Reader implements I{{pascalCase feature}}Reader {
|
||||
// Inject ReadOnly-branded use cases via constructor:
|
||||
//
|
||||
// constructor(
|
||||
// private getUser: ReadOnly<IGetUserUseCase>,
|
||||
// ) {}
|
||||
//
|
||||
// Then delegate:
|
||||
//
|
||||
// async exists(userId: string): Promise<boolean> {
|
||||
// const user = await this.getUser({ id: userId });
|
||||
// return user !== null;
|
||||
// }
|
||||
}
|
||||
Reference in New Issue
Block a user