@fougere/core 0.17.4-alpha.0 → 0.18.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (177) hide show
  1. package/dist/Declared.d.ts +2 -5
  2. package/dist/Declared.d.ts.map +1 -1
  3. package/dist/DeclaredHandler.d.ts +3 -8
  4. package/dist/DeclaredHandler.d.ts.map +1 -1
  5. package/dist/DeclaredSubject.d.ts +2 -5
  6. package/dist/DeclaredSubject.d.ts.map +1 -1
  7. package/dist/EffectiveOperation.d.ts +7 -6
  8. package/dist/EffectiveOperation.d.ts.map +1 -1
  9. package/dist/EffectiveOperationModel.d.ts.map +1 -1
  10. package/dist/EffectiveOperationModel.js +9 -6
  11. package/dist/EffectiveOperationModel.js.map +1 -1
  12. package/dist/FrondConfig.d.ts +0 -2
  13. package/dist/FrondConfig.d.ts.map +1 -1
  14. package/dist/FrondConfig.js.map +1 -1
  15. package/dist/FrondDeclaration.d.ts +3 -8
  16. package/dist/FrondDeclaration.d.ts.map +1 -1
  17. package/dist/FrondDeclaration.js +2 -1
  18. package/dist/FrondDeclaration.js.map +1 -1
  19. package/dist/OperationOverride.d.ts +5 -3
  20. package/dist/OperationOverride.d.ts.map +1 -1
  21. package/dist/SourceView.d.ts +10 -8
  22. package/dist/SourceView.d.ts.map +1 -1
  23. package/dist/SourceView.js +10 -1
  24. package/dist/SourceView.js.map +1 -1
  25. package/dist/boot/bootstrap.d.ts.map +1 -1
  26. package/dist/boot/bootstrap.js +5 -6
  27. package/dist/boot/bootstrap.js.map +1 -1
  28. package/dist/boot/unregistered.d.ts +4 -2
  29. package/dist/boot/unregistered.d.ts.map +1 -1
  30. package/dist/boot/unregistered.js +30 -30
  31. package/dist/boot/unregistered.js.map +1 -1
  32. package/dist/contract.d.ts +3 -0
  33. package/dist/contract.d.ts.map +1 -1
  34. package/dist/contract.js +3 -0
  35. package/dist/contract.js.map +1 -1
  36. package/dist/descriptor/CollectorEntry.d.ts +2 -7
  37. package/dist/descriptor/CollectorEntry.d.ts.map +1 -1
  38. package/dist/descriptor/FrondDescriptor.d.ts +5 -23
  39. package/dist/descriptor/FrondDescriptor.d.ts.map +1 -1
  40. package/dist/descriptor/FrondDescriptor.js.map +1 -1
  41. package/dist/descriptor/HandlerEntry.d.ts +2 -7
  42. package/dist/descriptor/HandlerEntry.d.ts.map +1 -1
  43. package/dist/descriptor/MiddlewareEntry.d.ts +2 -7
  44. package/dist/descriptor/MiddlewareEntry.d.ts.map +1 -1
  45. package/dist/descriptor/PresenterEntry.d.ts +2 -7
  46. package/dist/descriptor/PresenterEntry.d.ts.map +1 -1
  47. package/dist/descriptor/ProviderEntry.d.ts +2 -7
  48. package/dist/descriptor/ProviderEntry.d.ts.map +1 -1
  49. package/dist/descriptor/Subject.d.ts +10 -0
  50. package/dist/descriptor/Subject.d.ts.map +1 -0
  51. package/dist/descriptor/Subject.js +2 -0
  52. package/dist/descriptor/Subject.js.map +1 -0
  53. package/dist/descriptor/index.d.ts +1 -0
  54. package/dist/descriptor/index.d.ts.map +1 -1
  55. package/dist/descriptor/index.js +1 -0
  56. package/dist/descriptor/index.js.map +1 -1
  57. package/dist/index.d.ts +3 -1
  58. package/dist/index.d.ts.map +1 -1
  59. package/dist/index.js +2 -0
  60. package/dist/index.js.map +1 -1
  61. package/dist/prefab/AggregateConstructor.d.ts +2 -2
  62. package/dist/prefab/AggregateConstructor.d.ts.map +1 -1
  63. package/dist/prefab/AggregateOf.d.ts +2 -2
  64. package/dist/prefab/AggregateOf.d.ts.map +1 -1
  65. package/dist/prefab/AggregateShape.d.ts +2 -2
  66. package/dist/prefab/AggregateShape.d.ts.map +1 -1
  67. package/dist/prefab/CrudConstructor.d.ts +2 -2
  68. package/dist/prefab/CrudConstructor.d.ts.map +1 -1
  69. package/dist/prefab/CrudConstructor.js +12 -10
  70. package/dist/prefab/CrudConstructor.js.map +1 -1
  71. package/dist/prefab/CrudOps.d.ts +2 -2
  72. package/dist/prefab/CrudOps.d.ts.map +1 -1
  73. package/dist/prefab/CrudViews.d.ts +2 -2
  74. package/dist/prefab/CrudViews.d.ts.map +1 -1
  75. package/dist/prefab/MirrorConstructor.d.ts +2 -2
  76. package/dist/prefab/MirrorConstructor.d.ts.map +1 -1
  77. package/dist/prefab/MirrorConstructor.js.map +1 -1
  78. package/dist/prefab/RepositoryConstructor.d.ts +4 -4
  79. package/dist/prefab/RepositoryConstructor.d.ts.map +1 -1
  80. package/dist/prefab/RepositoryConstructor.js.map +1 -1
  81. package/dist/prefab/collector.d.ts +2 -1
  82. package/dist/prefab/collector.d.ts.map +1 -1
  83. package/dist/prefab/collector.js.map +1 -1
  84. package/dist/prefab/opsOf.d.ts +4 -0
  85. package/dist/prefab/opsOf.d.ts.map +1 -0
  86. package/dist/prefab/opsOf.js +5 -0
  87. package/dist/prefab/opsOf.js.map +1 -0
  88. package/dist/prefab/prefab.d.ts +3 -3
  89. package/dist/prefab/prefab.d.ts.map +1 -1
  90. package/dist/prefab/presenter.d.ts +3 -3
  91. package/dist/prefab/presenter.d.ts.map +1 -1
  92. package/dist/prefab/presenter.js.map +1 -1
  93. package/dist/wire/Addresses.d.ts +4 -4
  94. package/dist/wire/Addresses.d.ts.map +1 -1
  95. package/dist/wire/Cardinality.d.ts +4 -0
  96. package/dist/wire/Cardinality.d.ts.map +1 -0
  97. package/dist/wire/Cardinality.js +3 -0
  98. package/dist/wire/Cardinality.js.map +1 -0
  99. package/dist/wire/OperationContract.d.ts +2 -1
  100. package/dist/wire/OperationContract.d.ts.map +1 -1
  101. package/dist/wire/OperationContract.js.map +1 -1
  102. package/dist/wire/OperationKind.d.ts +3 -1
  103. package/dist/wire/OperationKind.d.ts.map +1 -1
  104. package/dist/wire/OperationKind.js +2 -1
  105. package/dist/wire/OperationKind.js.map +1 -1
  106. package/dist/wire/Placement.d.ts +4 -0
  107. package/dist/wire/Placement.d.ts.map +1 -0
  108. package/dist/wire/Placement.js +3 -0
  109. package/dist/wire/Placement.js.map +1 -0
  110. package/dist/wire/RouteKind.d.ts +2 -1
  111. package/dist/wire/RouteKind.d.ts.map +1 -1
  112. package/dist/wire/StateShape.d.ts +1 -2
  113. package/dist/wire/StateShape.d.ts.map +1 -1
  114. package/dist/wire/StateShape.js +11 -11
  115. package/dist/wire/StateShape.js.map +1 -1
  116. package/dist/wire/TypeRef.d.ts +5 -0
  117. package/dist/wire/TypeRef.d.ts.map +1 -1
  118. package/dist/wire/binding.d.ts +4 -1
  119. package/dist/wire/binding.d.ts.map +1 -1
  120. package/dist/wire/binding.js +4 -2
  121. package/dist/wire/binding.js.map +1 -1
  122. package/dist/wire/card/CardOp.d.ts +4 -10
  123. package/dist/wire/card/CardOp.d.ts.map +1 -1
  124. package/dist/wire/refusals.d.ts +2 -1
  125. package/dist/wire/refusals.d.ts.map +1 -1
  126. package/dist/wire/refusals.js.map +1 -1
  127. package/dist/wire/topology/DeclaredFrond.d.ts +2 -1
  128. package/dist/wire/topology/DeclaredFrond.d.ts.map +1 -1
  129. package/dist/wire/topology/FrondPlacement.d.ts +2 -1
  130. package/dist/wire/topology/FrondPlacement.d.ts.map +1 -1
  131. package/package.json +3 -3
  132. package/src/Declared.ts +3 -7
  133. package/src/DeclaredHandler.ts +3 -8
  134. package/src/DeclaredSubject.ts +3 -5
  135. package/src/EffectiveOperation.ts +7 -6
  136. package/src/EffectiveOperationModel.ts +11 -7
  137. package/src/FrondConfig.ts +0 -2
  138. package/src/FrondDeclaration.ts +5 -8
  139. package/src/OperationOverride.ts +5 -3
  140. package/src/SourceView.ts +21 -3
  141. package/src/boot/bootstrap.ts +7 -6
  142. package/src/boot/unregistered.ts +31 -33
  143. package/src/contract.ts +3 -0
  144. package/src/descriptor/CollectorEntry.ts +2 -7
  145. package/src/descriptor/FrondDescriptor.ts +5 -19
  146. package/src/descriptor/HandlerEntry.ts +2 -7
  147. package/src/descriptor/MiddlewareEntry.ts +2 -7
  148. package/src/descriptor/PresenterEntry.ts +2 -7
  149. package/src/descriptor/ProviderEntry.ts +2 -7
  150. package/src/descriptor/Subject.ts +10 -0
  151. package/src/descriptor/index.ts +1 -0
  152. package/src/index.ts +3 -1
  153. package/src/prefab/AggregateConstructor.ts +2 -2
  154. package/src/prefab/AggregateOf.ts +2 -2
  155. package/src/prefab/AggregateShape.ts +2 -2
  156. package/src/prefab/CrudConstructor.ts +17 -13
  157. package/src/prefab/CrudOps.ts +3 -3
  158. package/src/prefab/CrudViews.ts +2 -2
  159. package/src/prefab/MirrorConstructor.ts +2 -2
  160. package/src/prefab/RepositoryConstructor.ts +9 -9
  161. package/src/prefab/collector.ts +4 -3
  162. package/src/prefab/opsOf.ts +6 -0
  163. package/src/prefab/prefab.ts +5 -5
  164. package/src/prefab/presenter.ts +3 -3
  165. package/src/wire/Addresses.ts +4 -4
  166. package/src/wire/Cardinality.ts +4 -0
  167. package/src/wire/OperationContract.ts +2 -1
  168. package/src/wire/OperationKind.ts +4 -1
  169. package/src/wire/Placement.ts +4 -0
  170. package/src/wire/RouteKind.ts +3 -1
  171. package/src/wire/StateShape.ts +9 -11
  172. package/src/wire/TypeRef.ts +2 -0
  173. package/src/wire/binding.ts +7 -2
  174. package/src/wire/card/CardOp.ts +4 -10
  175. package/src/wire/refusals.ts +2 -1
  176. package/src/wire/topology/DeclaredFrond.ts +3 -1
  177. package/src/wire/topology/FrondPlacement.ts +3 -1
package/src/contract.ts CHANGED
@@ -53,6 +53,9 @@ export type { DeclaredEdge } from './wire/topology/DeclaredEdge.js';
53
53
  export type { DeclaredFrond } from './wire/topology/DeclaredFrond.js';
54
54
  export type { DeclaredTopology } from './wire/topology/DeclaredTopology.js';
55
55
  export type { Edge } from './wire/topology/Edge.js';
56
+ export { OPERATION_KINDS, type OperationKind } from './wire/OperationKind.js';
57
+ export { CARDINALITIES, type Cardinality } from './wire/Cardinality.js';
58
+ export { PLACEMENTS, type Placement } from './wire/Placement.js';
56
59
  export type { FrondPlacement } from './wire/topology/FrondPlacement.js';
57
60
  export type { TopologyReport } from './wire/topology/TopologyReport.js';
58
61
  export { assertIdentityCard } from './wire/card/IdentityCard.js';
@@ -1,11 +1,6 @@
1
+ import type { Subject } from './Subject.js';
1
2
  /** A discovered collector (resolves handler input params from invocation context). */
2
- export interface CollectorEntry {
3
+ export interface CollectorEntry extends Subject {
3
4
  /** Registration key of the TYPE this collector resolves — 'user', 'ability'. */
4
5
  typeName: string;
5
- /** The collector class. */
6
- ctor: new (...args: never[]) => unknown;
7
- /** Constructor dependency type names (from AST scan). */
8
- deps: string[];
9
- /** Absolute file path (for debugging). */
10
- filePath: string;
11
6
  }
@@ -1,4 +1,6 @@
1
1
  import type { OperationContract } from '../wire/OperationContract.js';
2
+ import type { OperationOverride } from '../OperationOverride.js';
3
+ import type { FrondConfig } from '../FrondConfig.js';
2
4
  import type { ProviderEntry } from './ProviderEntry.js';
3
5
  import type { EntityEntry } from './EntityEntry.js';
4
6
  import type { HandlerEntry } from './HandlerEntry.js';
@@ -15,7 +17,7 @@ import type { ExtensionEntry } from './ExtensionEntry.js';
15
17
  */
16
18
  export const nameOf = (provider: ProviderEntry): string => provider.name ?? provider.ctor.name;
17
19
 
18
- export interface FrondDescriptor {
20
+ export interface FrondDescriptor extends Pick<FrondConfig, 'pipes' | 'surfaces' | 'reads'> {
19
21
  name: string;
20
22
  source: FrondSource;
21
23
  providers: ProviderEntry[];
@@ -41,26 +43,10 @@ export interface FrondDescriptor {
41
43
  * flat, and a directory says nothing about who shares its code.
42
44
  */
43
45
  extends?: string;
44
- /** The ops that finish a fact, in order — see `FrondConfig.pipes`. */
45
- pipes?: Record<string, string[]>;
46
- /**
47
- * Per-surface entity lists from frond.config.ts (e.g. { graphql: ['Post'], rest: ['Post',
48
- * 'Author'] }).
49
- */
50
- surfaces?: Record<string, string[]>;
51
- /** Entities this frond may read across sources — see `FrondConfig.reads`. */
52
- reads?: string[];
53
46
  /** Per-operation overrides from frond.config.ts. */
54
- operationsOverrides?: Record<string, OperationContract & {
55
- kind?: 'query' | 'command';
56
- /** Class name to resolve from DI (overrides default `{Entity}Handler` lookup). */
47
+ operationsOverrides?: Record<string, OperationContract & Omit<OperationOverride, 'handler'> & {
48
+ /** The handler CLASS of `frond.config.ts`, by the name DI resolves it under. */
57
49
  handlerName?: string;
58
- /** Method name on the resolved handler (defaults to op name). */
59
- method?: string;
60
- /** The GraphQL root field this op answers to — read by `adapter/graphql`. */
61
- graphql?: string;
62
- /** Where it answers over REST — read by `adapter/rest`, the dual of `graphql`. */
63
- rest?: { method?: string; path?: string; status?: number };
64
50
  }>;
65
51
  }
66
52
 
@@ -1,8 +1,9 @@
1
+ import type { Subject } from './Subject.js';
1
2
  import type { SchemaView } from '@fougere/schema';
2
3
  import type { OperationsMap } from '../wire/OperationsMap.js';
3
4
 
4
5
  /** A discovered handler (controls what a service exposes). */
5
- export interface HandlerEntry {
6
+ export interface HandlerEntry extends Subject {
6
7
  /** Registration key (e.g. 'postHandler'). */
7
8
  name: string;
8
9
  /**
@@ -10,14 +11,8 @@ export interface HandlerEntry {
10
11
  * `post`).
11
12
  */
12
13
  address: string;
13
- /** The handler class. */
14
- ctor: new (...args: never[]) => unknown;
15
14
  /** All operations with full signatures for binding. */
16
15
  operations: OperationsMap;
17
- /** Constructor dependency type names (from AST scan). */
18
- deps: string[];
19
- /** Absolute file path (for debugging). */
20
- filePath: string;
21
16
  /** Whether this handler is part of the frond's public contract. */
22
17
  exposed?: boolean;
23
18
  /** Output schema override — when Crud(Entity, Output), restricts storage output. */
@@ -1,3 +1,4 @@
1
+ import type { Subject } from './Subject.js';
1
2
  /**
2
3
  * A discovered middleware — a class declaring `around(ctx, next)`, which runs before and after
3
4
  * every operation of its frond, and of the fronds that inherit from it.
@@ -5,13 +6,7 @@
5
6
  * How far it reaches is not written here: it is where the frond sits in `FougereConfig.fronds`,
6
7
  * and stating it twice would put one decision in two places.
7
8
  */
8
- export interface MiddlewareEntry {
9
+ export interface MiddlewareEntry extends Subject {
9
10
  /** Class name — the key it registers under in its frond's scope. */
10
11
  name: string;
11
- /** The middleware class. */
12
- ctor: new (...args: never[]) => unknown;
13
- /** Constructor dependency type names (from AST scan). */
14
- deps: string[];
15
- /** Absolute file path (for debugging). */
16
- filePath: string;
17
12
  }
@@ -1,12 +1,11 @@
1
+ import type { Subject } from './Subject.js';
1
2
  import type { PresenterViews } from '../prefab/presenter.js';
2
3
  import type { PresenterFieldMeta } from './PresenterFieldMeta.js';
3
4
 
4
5
  /** A discovered presenter (computed fields for an entity's output). */
5
- export interface PresenterEntry {
6
+ export interface PresenterEntry extends Subject {
6
7
  /** Entity name this presenter enriches (e.g. 'post' from PostPresenter). */
7
8
  entityName: string;
8
- /** The presenter class. */
9
- ctor: new (...args: never[]) => unknown;
10
9
  /** Computed field names (method names on prototype). */
11
10
  fields: string[];
12
11
  /** Per-field type metadata (inferred from source via parser). */
@@ -16,8 +15,4 @@ export interface PresenterEntry {
16
15
  * items: [OrderItemView] })`).
17
16
  */
18
17
  views?: PresenterViews;
19
- /** Constructor dependency type names (from AST scan). */
20
- deps: string[];
21
- /** Absolute file path (for debugging). */
22
- filePath: string;
23
18
  }
@@ -1,5 +1,6 @@
1
+ import type { Subject } from './Subject.js';
1
2
  /** A discovered provider — a class under `services/` or `repositories/`, injected by type. */
2
- export interface ProviderEntry {
3
+ export interface ProviderEntry extends Subject {
3
4
  /**
4
5
  * The container key — the class name as the SOURCE spells it.
5
6
  *
@@ -9,12 +10,6 @@ export interface ProviderEntry {
9
10
  * A name a tool may rewrite is written down, for the same reason `deps` is.
10
11
  */
11
12
  name?: string;
12
- /** The class constructor (default export of the file). */
13
- ctor: new (...args: never[]) => unknown;
14
- /** Constructor dependency type names (from AST scan). */
15
- deps: string[];
16
- /** Absolute file path (for debugging). */
17
- filePath: string;
18
13
  /**
19
14
  * What the source said and the runtime erased: a port, which a class below answers for and
20
15
  * nothing may instantiate — never a candidate for the ports above it.
@@ -0,0 +1,10 @@
1
+ import type { Ctor } from '../Ctor.js';
2
+
3
+ /** A class the boot builds — what its constructor asks for, and where the scan found it. */
4
+ export interface Subject {
5
+ ctor: Ctor;
6
+ /** Constructor dependency type names (from AST scan). */
7
+ deps: string[];
8
+ /** Absolute file path (for debugging). */
9
+ filePath: string;
10
+ }
@@ -21,6 +21,7 @@ export { type OperationsMap } from '../wire/OperationsMap.js';
21
21
  export { type Param } from '../wire/Param.js';
22
22
  export { type TypeRef } from '../wire/TypeRef.js';
23
23
  export { computeBindingPlan, type BindingPlan } from '../wire/binding.js';
24
+ export { opsOf } from '../prefab/opsOf.js';
24
25
  export type { Signature } from '../wire/Signature.js';
25
26
  export { awaitKeyOf, emitKeyOf } from '../wire/Emit.js';
26
27
  export { addressOf } from '../wire/Facade.js';
package/src/index.ts CHANGED
@@ -63,6 +63,7 @@ export type { AppMiddleware } from './wire/AppMiddleware.js';
63
63
  export type { AppNext } from './wire/AppNext.js';
64
64
  export type { OperationContext } from './wire/OperationContext.js';
65
65
  export { assertIdentityCard, RPC_ADDRESS, ENVELOPE_BYTES, maxBodyBytes, maxFrameBytes, setMaxBodyBytes } from './contract.js';
66
+ export { OPERATION_KINDS, CARDINALITIES, PLACEMENTS, type OperationKind, type Cardinality, type Placement } from './contract.js';
66
67
  export { createLocalRunner, createAppRunner } from './boot/runner.js';
67
68
 
68
69
  // A frond an app STATES, for a boot that will not scan. No disk, no `typescript` — which
@@ -81,6 +82,7 @@ export { declaredTopologyOf } from './boot/declared.js';
81
82
  // Both halves of what a call can refuse, put together where they are READ — the framework's
82
83
  // follows from `kind` and `input`, so only the frond's travels.
83
84
  export { refusalsOf, type Refusable } from './wire/refusals.js';
85
+ export type { Binding } from './Binding.js';
84
86
  // The facades as TYPES — empty here, filled by the `.d.ts` the scan writes beside the app.
85
87
  export type { Addresses } from './wire/Addresses.js';
86
88
  export type { Answer } from './wire/Answer.js';
@@ -129,7 +131,7 @@ export { Storage, togetherKeyOf } from './storage/Storage.js';
129
131
  export type { Constraint } from './Constraint.js';
130
132
  export type { Source } from './Source.js';
131
133
  export type { SourceConfig } from './SourceConfig.js';
132
- export type { SourceView } from './SourceView.js';
134
+ export { sourceViewOf, type SourceView } from './SourceView.js';
133
135
  export { Sources } from './Source.js';
134
136
  export { storageOver } from './storage/Store.js';
135
137
  export type { Store } from './storage/Store.js';
@@ -1,7 +1,7 @@
1
1
  import type { AggregateShape } from './AggregateShape.js';
2
- import { type EntityConstructor } from '@fougere/schema';
2
+ import { type Entity } from '@fougere/schema';
3
3
 
4
- export type AggregateConstructor<E extends readonly EntityConstructor[]> =
4
+ export type AggregateConstructor<E extends readonly Entity[]> =
5
5
  (new (...storages: unknown[]) => AggregateShape<E>) & {
6
6
  readonly __entity: unknown;
7
7
  /** The entities this class owns. Present from two on — an owner of one owns nothing. */
@@ -1,6 +1,6 @@
1
- import { type EntityConstructor } from '@fougere/schema';
1
+ import { type Entity } from '@fougere/schema';
2
2
  import type { Storage } from '../storage/Storage.js';
3
3
 
4
4
  /** The tuple an aggregate holds — the readable form of {@link AggregateShape}'s member. */
5
- export type AggregateOf<E extends readonly EntityConstructor[]> =
5
+ export type AggregateOf<E extends readonly Entity[]> =
6
6
  { [K in keyof E]: Storage<InstanceType<E[K]>> };
@@ -1,7 +1,7 @@
1
1
  import type { Storage } from '../storage/Storage.js';
2
- import type { EntityConstructor } from '@fougere/schema';
2
+ import type { Entity } from '@fougere/schema';
3
3
 
4
4
  /** What an aggregate's subclass sees. */
5
- export declare abstract class AggregateShape<E extends readonly EntityConstructor[]> {
5
+ export declare abstract class AggregateShape<E extends readonly Entity[]> {
6
6
  protected storages: { [K in keyof E]: Storage<InstanceType<E[K]>> };
7
7
  }
@@ -1,4 +1,4 @@
1
- import { lowerFirst, type EntityConstructor, type SchemaView } from '@fougere/schema';
1
+ import { FieldSet, lowerFirst, type Entity, type SchemaView } from '@fougere/schema';
2
2
  import type { ListOptions } from '../storage/ListOptions.js';
3
3
  import type { Storage } from '../storage/Storage.js';
4
4
  import type { OperationContract } from '../wire/OperationContract.js';
@@ -29,13 +29,15 @@ export function subjectOf(ctor: unknown, address: string): string {
29
29
  return target?.name ? lowerFirst(target.name) : address;
30
30
  }
31
31
 
32
- /** The id of the row an op acts on — a route segment, or a query fallback. */
33
- const byId = { name: 'id', source: { kind: 'param' as const, name: 'id' }, optional: false };
32
+ /** The id of the row an op acts on — `id: Post['id']`, which a route places before the op. */
33
+ const byId = (entity: string) =>
34
+ ({ name: 'id', source: { kind: 'param' as const, name: 'id', identifies: entity }, optional: false });
34
35
 
35
36
  const fromBody = { name: 'input', source: { kind: 'input' as const }, optional: false };
36
37
 
37
38
  /** The same five, written as the scan would have written them. */
38
- const idParam = { name: 'id', type: { raw: 'string', name: 'string' } };
39
+ const idParam = (entity: string, field: string) =>
40
+ ({ name: 'id', type: { raw: `${entity}['${field}']`, name: 'string', identifies: { entity, field } } });
39
41
 
40
42
  const inputParam = (entity: string) => ({ name: 'input', type: { raw: `Partial<${entity}>`, name: entity } });
41
43
 
@@ -50,6 +52,8 @@ const returns = (raw: string, name: string, extra?: { array?: boolean; nullable?
50
52
  function crudOps(entity: SchemaView & { partial?: () => SchemaView }): Record<string, OperationContract> {
51
53
  const name = (entity as { name?: string }).name ?? 'Entity';
52
54
  const input = inputParam(name);
55
+ const id = byId(name);
56
+ const idSignature = idParam(name, FieldSet.of(entity.getFields()).primary ?? 'id');
53
57
 
54
58
  return {
55
59
  list: {
@@ -61,8 +65,8 @@ function crudOps(entity: SchemaView & { partial?: () => SchemaView }): Record<st
61
65
  },
62
66
  },
63
67
  findById: {
64
- output: entity, cardinality: 'maybe', binding: [byId],
65
- signature: { name: 'findById', returnType: returns(`${name} | undefined`, name, { nullable: true }), params: [idParam] },
68
+ output: entity, cardinality: 'maybe', binding: [id],
69
+ signature: { name: 'findById', returnType: returns(`${name} | undefined`, name, { nullable: true }), params: [idSignature] },
66
70
  },
67
71
  create: {
68
72
  input: entity, output: entity, cardinality: 'one', binding: [fromBody],
@@ -71,12 +75,12 @@ function crudOps(entity: SchemaView & { partial?: () => SchemaView }): Record<st
71
75
  // The patch view carries its own mode: an absent field is untouched, an
72
76
  // immutable one re-supplied is refused.
73
77
  update: {
74
- input: entity.partial?.(), output: entity, cardinality: 'one', binding: [byId, fromBody],
75
- signature: { name: 'update', returnType: returns(name, name), params: [idParam, input] },
78
+ input: entity.partial?.(), output: entity, cardinality: 'one', binding: [id, fromBody],
79
+ signature: { name: 'update', returnType: returns(name, name), params: [idSignature, input] },
76
80
  },
77
81
  delete: {
78
- cardinality: 'none', binding: [byId],
79
- signature: { name: 'delete', returnType: returns('boolean', 'boolean'), params: [idParam] },
82
+ cardinality: 'none', binding: [id],
83
+ signature: { name: 'delete', returnType: returns('boolean', 'boolean'), params: [idSignature] },
80
84
  },
81
85
  };
82
86
  }
@@ -103,7 +107,7 @@ export interface CrudConstructor<T, V = {}> {
103
107
  *
104
108
  * Documented: [handlers](https://fougere.dev/docs/business/handlers).
105
109
  */
106
- export function Crud<E extends EntityConstructor, V extends CrudViews | EntityConstructor = {}>(
110
+ export function Crud<E extends Entity, V extends CrudViews | Entity = {}>(
107
111
  entity: E,
108
112
  output?: V,
109
113
  ): CrudConstructor<InstanceType<E>, V> {
@@ -112,7 +116,7 @@ export function Crud<E extends EntityConstructor, V extends CrudViews | EntityCo
112
116
 
113
117
  // A view is a class (it carries fields) ; a map of views is a plain object.
114
118
  const perOp = typeof output === 'object' && output !== null ? (output as CrudViews) : undefined;
115
- const wholeHandler = typeof output === 'function' ? (output as EntityConstructor) : undefined;
119
+ const wholeHandler = typeof output === 'function' ? (output as Entity) : undefined;
116
120
 
117
121
  return asCrudConstructor<T, V>(class CrudHandler {
118
122
  static __entity = entity;
@@ -123,7 +127,7 @@ export function Crud<E extends EntityConstructor, V extends CrudViews | EntityCo
123
127
  * What this prefab handler declares — read by the façade, merged under the author's own
124
128
  * methods.
125
129
  */
126
- static __ops: Record<string, OperationContract> = crudOps(entity as unknown as SchemaView & { partial?: () => SchemaView });
130
+ static __ops: Record<string, OperationContract> = crudOps(entity);
127
131
 
128
132
  // Private: the five ops are how a subclass reaches the rows — `super.findById(id)` — and a
129
133
  // query worth a name belongs to the repository, which it asks for like any handler does.
@@ -1,5 +1,5 @@
1
1
  import type { CrudOpName } from './CrudOpName.js';
2
- import type { EntityConstructor } from '@fougere/schema';
2
+ import type { Entity } from '@fougere/schema';
3
3
  import type { ListOptions } from '../storage/ListOptions.js';
4
4
  import type { Page } from '../wire/Page.js';
5
5
 
@@ -8,8 +8,8 @@ type OutOf<V, K extends CrudOpName, T> =
8
8
  // Bracketed on purpose: a naked `V extends …` DISTRIBUTES, and the no-view default
9
9
  // is the empty map, whose `keyof` is `never` — distribution would then collapse
10
10
  // every op's output to `never` instead of falling through to the entity.
11
- [V] extends [EntityConstructor] ? InstanceType<V & EntityConstructor>
12
- : K extends keyof V ? (V[K] extends EntityConstructor ? InstanceType<V[K]> : T)
11
+ [V] extends [Entity] ? InstanceType<V & Entity>
12
+ : K extends keyof V ? (V[K] extends Entity ? InstanceType<V[K]> : T)
13
13
  : T;
14
14
 
15
15
  /** The five ops, typed from the entity and its views. */
@@ -1,5 +1,5 @@
1
- import { type EntityConstructor } from '@fougere/schema';
1
+ import { type Entity } from '@fougere/schema';
2
2
  import type { CrudOpName } from './CrudOpName.js';
3
3
 
4
4
  /** Which view each op speaks — omitted ops speak the entity, the trivial view. */
5
- export type CrudViews = Partial<Record<CrudOpName, EntityConstructor>>;
5
+ export type CrudViews = Partial<Record<CrudOpName, Entity>>;
@@ -1,4 +1,4 @@
1
- import type { EntityConstructor } from '@fougere/schema';
1
+ import type { Entity } from '@fougere/schema';
2
2
  import type { Storage } from '../storage/Storage.js';
3
3
  import type { Refreshed } from './Refreshed.js';
4
4
  import type { MirrorOf } from './MirrorOf.js';
@@ -8,7 +8,7 @@ export interface MirrorConstructor<T> {
8
8
  readonly __entity: unknown;
9
9
  }
10
10
 
11
- export function Mirror<E extends EntityConstructor>(shape: E): MirrorConstructor<InstanceType<E>> {
11
+ export function Mirror<E extends Entity>(shape: E): MirrorConstructor<InstanceType<E>> {
12
12
  type T = InstanceType<E>;
13
13
 
14
14
  abstract class MirrorBase implements MirrorOf<T> {
@@ -1,4 +1,4 @@
1
- import { upperFirst, type EntityConstructor } from '@fougere/schema';
1
+ import { upperFirst, type Entity } from '@fougere/schema';
2
2
  import type { ListOptions } from '../storage/ListOptions.js';
3
3
  import type { ListResult } from '../storage/ListResult.js';
4
4
  import type { SelectOption } from '../storage/SelectOption.js';
@@ -14,22 +14,22 @@ export interface RepositoryConstructor<T> {
14
14
  }
15
15
 
16
16
  /** What an aggregate's subclass sees. */
17
- declare abstract class AggregateShape<E extends readonly EntityConstructor[]> {
17
+ declare abstract class AggregateShape<E extends readonly Entity[]> {
18
18
  protected storages: { [K in keyof E]: Storage<InstanceType<E[K]>> };
19
19
  }
20
20
 
21
- export function Repository<E extends EntityConstructor>(entity: E): RepositoryConstructor<InstanceType<E>>;
21
+ export function Repository<E extends Entity>(entity: E): RepositoryConstructor<InstanceType<E>>;
22
22
 
23
- export function Repository<E extends readonly [EntityConstructor, EntityConstructor, ...EntityConstructor[]]>(
23
+ export function Repository<E extends readonly [Entity, Entity, ...Entity[]]>(
24
24
  ...entities: E
25
25
  ): AggregateConstructor<E>;
26
26
 
27
- export function Repository(...entities: EntityConstructor[]): unknown {
27
+ export function Repository(...entities: Entity[]): unknown {
28
28
  return entities.length === 1 ? one(entities[0]) : many(entities);
29
29
  }
30
30
 
31
31
  /** The port forwarded, member by member. */
32
- function one<E extends EntityConstructor>(entity: E): RepositoryConstructor<InstanceType<E>> {
32
+ function one<E extends Entity>(entity: E): RepositoryConstructor<InstanceType<E>> {
33
33
  type T = InstanceType<E>;
34
34
 
35
35
  class RepositoryBase implements Storage<T> {
@@ -56,7 +56,7 @@ function one<E extends EntityConstructor>(entity: E): RepositoryConstructor<Inst
56
56
  }
57
57
 
58
58
  /** The aggregate. */
59
- function many<E extends readonly EntityConstructor[]>(entities: E): AggregateConstructor<E> {
59
+ function many<E extends readonly Entity[]>(entities: E): AggregateConstructor<E> {
60
60
  class AggregateBase {
61
61
  static readonly __entity = entities[0];
62
62
  static readonly __owns = entities;
@@ -77,6 +77,6 @@ export function repositoryKeyOf(entity: string): string {
77
77
  }
78
78
 
79
79
  /** The entities a class owns — empty for anything that is not an aggregate. */
80
- export function ownedBy(ctor: unknown): readonly EntityConstructor[] {
81
- return (ctor as { __owns?: readonly EntityConstructor[] } | undefined)?.__owns ?? [];
80
+ export function ownedBy(ctor: unknown): readonly Entity[] {
81
+ return (ctor as { __owns?: readonly Entity[] } | undefined)?.__owns ?? [];
82
82
  }
@@ -4,11 +4,12 @@
4
4
  * Documented: [collectors](https://fougere.dev/docs/business/collectors).
5
5
  */
6
6
 
7
- import { FieldValueValidator, dotted, json, upperFirst, type Field, type SchemaView } from '@fougere/schema';
7
+ import type { AbstractCtor } from '../Ctor.js';
8
+ import { FieldValueValidator, dotted, json, upperFirst, type Entity, type Field, type SchemaView } from '@fougere/schema';
8
9
  import { ErrorCode } from '../wire/ErrorCode.js';
9
10
  import { FougereError } from '../wire/FougereError.js';
10
11
 
11
- export function Collector<T extends abstract new (...args: any[]) => any>(target: T) {
12
+ export function Collector<T extends AbstractCtor>(target: T) {
12
13
  class CollectorBase {
13
14
  static readonly __entity = target;
14
15
  }
@@ -34,7 +35,7 @@ export function collectedAs(collector: object, value: unknown): unknown {
34
35
 
35
36
  let reader = readers.get(target);
36
37
  if (!reader) {
37
- reader = json(target as SchemaView & (new () => unknown));
38
+ reader = json(target as Entity);
38
39
  readers.set(target, reader);
39
40
  }
40
41
 
@@ -0,0 +1,6 @@
1
+ import type { OperationContract } from '../wire/OperationContract.js';
2
+
3
+ /** The contracts a class carries on itself — `Crud(Post)` states its five as `__ops`. */
4
+ export function opsOf(ctor: unknown): Record<string, OperationContract> {
5
+ return (ctor as { __ops?: Record<string, OperationContract> }).__ops ?? {};
6
+ }
@@ -1,18 +1,18 @@
1
- import type { EntityConstructor } from '@fougere/schema';
1
+ import type { Entity } from '@fougere/schema';
2
2
  import type { PresenterViews } from './presenter.js';
3
3
 
4
4
  /** What a prefab class declares about ITSELF, at runtime. */
5
5
  interface Prefab {
6
6
  /** The shape this class was built on. `Mirror` calls it a shape; it is the same slot. */
7
- readonly __entity?: EntityConstructor;
7
+ readonly __entity?: Entity;
8
8
  /** The view each computed field emits — `Presenter(Order, { items: [OrderItemView] })`. */
9
9
  readonly __views?: PresenterViews;
10
10
  /** The view the whole handler speaks — `Crud(Post, PostPublic)`. Same slot, one level up. */
11
- readonly __output?: EntityConstructor;
11
+ readonly __output?: Entity;
12
12
  }
13
13
 
14
14
  /** The shape a prefab was built on, or `undefined` when the class is not one. */
15
- export function targetOf(ctor: unknown): EntityConstructor | undefined {
15
+ export function targetOf(ctor: unknown): Entity | undefined {
16
16
  return (ctor as Prefab | undefined)?.__entity;
17
17
  }
18
18
 
@@ -22,6 +22,6 @@ export function viewsOf(ctor: unknown): PresenterViews | undefined {
22
22
  }
23
23
 
24
24
  /** The handler-wide view a prefab declares, same rule. */
25
- export function outputOf(ctor: unknown): EntityConstructor | undefined {
25
+ export function outputOf(ctor: unknown): Entity | undefined {
26
26
  return (ctor as Prefab | undefined)?.__output;
27
27
  }
@@ -4,16 +4,16 @@
4
4
  * Documented: [presenters](https://fougere.dev/docs/business/presenters).
5
5
  */
6
6
 
7
- import { upperFirst, type EntityConstructor } from '@fougere/schema';
7
+ import { upperFirst, type Entity } from '@fougere/schema';
8
8
 
9
9
  /**
10
10
  * The view a computed field emits — `OrderItemView` for one, `[OrderItemView]` for many.
11
11
  * A view is a schema, so it derives: `Order.pick('id', 'status')`, never a hand-written type.
12
12
  */
13
- export type PresenterViews = Record<string, EntityConstructor | [EntityConstructor]>;
13
+ export type PresenterViews = Record<string, Entity | [Entity]>;
14
14
 
15
15
  /** `Presenter(Order, { items: [OrderItemView] })` — the views its computed fields emit. */
16
- export function Presenter<E extends EntityConstructor>(entity: E, views?: PresenterViews) {
16
+ export function Presenter<E extends Entity>(entity: E, views?: PresenterViews) {
17
17
  class PresenterBase {
18
18
  static readonly __entity = entity;
19
19
  static readonly __views = views;
@@ -1,17 +1,17 @@
1
1
  import type { FougereOperations } from './FougereOperations.js';
2
2
 
3
- type Served = keyof FougereOperations & string;
3
+ type Generated = keyof FougereOperations & string;
4
4
 
5
5
  /**
6
6
  * Both halves below take the union through a type PARAMETER, which is what makes the
7
- * conditional distribute: `Served` is an alias, and an alias is matched whole — `'a.x' | 'b.y'`
7
+ * conditional distribute: `Generated` is an alias, and an alias is matched whole — `'a.x' | 'b.y'`
8
8
  * does not extend `` `${string}.${string}` `` as one thing, so the naive form answers `never`.
9
9
  */
10
10
  type AddressIn<Key> = Key extends `${infer Address}.${string}` ? Address : never;
11
11
 
12
12
  /**
13
13
  * The addresses this app answers at — or any string, until a scan has said otherwise. The
14
- * `[Served] extends [never]` form is what keeps an ungenerated project compiling: a bare `never`
14
+ * `[Generated] extends [never]` form is what keeps an ungenerated project compiling: a bare `never`
15
15
  * in a parameter position refuses every call, the correct ones included.
16
16
  */
17
- export type Addresses = [Served] extends [never] ? string : AddressIn<Served>;
17
+ export type Addresses = [Generated] extends [never] ? string : AddressIn<Generated>;
@@ -0,0 +1,4 @@
1
+ /** How MUCH an operation answers — the half of a return type that a view cannot say. */
2
+ export const CARDINALITIES = ['one', 'maybe', 'many', 'page', 'none'] as const;
3
+
4
+ export type Cardinality = (typeof CARDINALITIES)[number];
@@ -5,6 +5,7 @@ import type { TypeRef } from './TypeRef.js';
5
5
  import type { ErrorCode } from './ErrorCode.js';
6
6
  import type { OperationKind } from './OperationKind.js';
7
7
  import type { OperationKindInference } from './OperationKindInference.js';
8
+ import type { Cardinality } from './Cardinality.js';
8
9
 
9
10
  /** The contract of one operation — everything the façade needs to serve a call. */
10
11
  export interface OperationContract {
@@ -17,7 +18,7 @@ export interface OperationContract {
17
18
  /** The operation in words, for a caller that meets it over the wire and has to choose. */
18
19
  description?: string;
19
20
  /** How MUCH comes back — the half of the return type that `output` cannot say. */
20
- cardinality?: 'one' | 'maybe' | 'many' | 'page' | 'none';
21
+ cardinality?: Cardinality;
21
22
  /** The argument names and TYPES — the one thing `binding` cannot say. */
22
23
  signature?: Signature;
23
24
  /**
@@ -1 +1,4 @@
1
- export type OperationKind = 'query' | 'command';
1
+ /** `query` reads, `command` writes — the same call REST turns into GET vs POST. */
2
+ export const OPERATION_KINDS = ['query', 'command'] as const;
3
+
4
+ export type OperationKind = (typeof OPERATION_KINDS)[number];
@@ -0,0 +1,4 @@
1
+ /** Where something answers — in this process, or a process away. */
2
+ export const PLACEMENTS = ['local', 'remote'] as const;
3
+
4
+ export type Placement = (typeof PLACEMENTS)[number];
@@ -1,2 +1,4 @@
1
+ import type { Placement } from './Placement.js';
2
+
1
3
  /** Which kind of destination served a call — it travels in a `CallRecord`. */
2
- export type RouteKind = 'local' | 'remote' | 'system';
4
+ export type RouteKind = Placement | 'system';
@@ -1,4 +1,4 @@
1
- import { InputValidator, dotted, optional, type Field, type Fields } from '@fougere/schema';
1
+ import { Schema, dotted, optional, type Field, type Fields, type SchemaConstructor, type SchemaView } from '@fougere/schema';
2
2
  import { ErrorCode } from './ErrorCode.js';
3
3
  import { FougereError } from './FougereError.js';
4
4
 
@@ -16,19 +16,16 @@ export interface StateDeclaration {
16
16
  * Documented: [collectors](https://fougere.dev/docs/business/collectors).
17
17
  */
18
18
  export class StateShape {
19
- static readonly empty = new StateShape({});
19
+ static readonly empty = new StateShape(Schema.of({ fields: {} }));
20
20
 
21
- private readonly validator: InputValidator;
22
-
23
- private constructor(private readonly fields: Fields) {
24
- this.validator = InputValidator.of(fields);
25
- }
21
+ private constructor(private readonly schema: SchemaView) {}
26
22
 
27
23
  /** Two extensions declaring one member refuse: the field that judges it would depend on wiring order. */
28
24
  static of(declarations: readonly StateDeclaration[]): StateShape {
29
- const fields: Fields = {};
30
25
  const owners = new Map<string, string>();
26
+ let schema: SchemaConstructor<Fields> = Schema.of({ fields: {} as Fields });
31
27
  for (const { name, state } of declarations) {
28
+ const fields: Fields = {};
32
29
  for (const [member, field] of Object.entries(state ?? {})) {
33
30
  const owner = owners.get(member);
34
31
  if (owner) {
@@ -40,9 +37,10 @@ export class StateShape {
40
37
  owners.set(member, name);
41
38
  fields[member] = optional(field);
42
39
  }
40
+ schema = schema.extend(fields);
43
41
  }
44
42
 
45
- return new StateShape(fields);
43
+ return new StateShape(schema);
46
44
  }
47
45
 
48
46
  /**
@@ -58,10 +56,10 @@ export class StateShape {
58
56
  const written = Object.fromEntries(Object.entries(state).filter(([member, value]) => entered[member] !== value));
59
57
  if (Object.keys(written).length === 0) return state;
60
58
 
61
- const result = this.validator.validate(written);
59
+ const result = this.schema.validate(written);
62
60
  if (result.success) return { ...state, ...result.data };
63
61
 
64
- const declared = Object.keys(this.fields);
62
+ const declared = Object.keys(this.schema.getFields());
65
63
  const errors = result.errors.map((error) => ({ ...error, path: ['state', ...error.path] }));
66
64
 
67
65
  throw new FougereError({