@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
@@ -1,4 +1,4 @@
1
- import { InputValidator, dotted, optional } from '@fougere/schema';
1
+ import { Schema, dotted, optional } from '@fougere/schema';
2
2
  import { ErrorCode } from './ErrorCode.js';
3
3
  import { FougereError } from './FougereError.js';
4
4
  /**
@@ -9,18 +9,17 @@ import { FougereError } from './FougereError.js';
9
9
  * Documented: [collectors](https://fougere.dev/docs/business/collectors).
10
10
  */
11
11
  export class StateShape {
12
- fields;
13
- static empty = new StateShape({});
14
- validator;
15
- constructor(fields) {
16
- this.fields = fields;
17
- this.validator = InputValidator.of(fields);
12
+ schema;
13
+ static empty = new StateShape(Schema.of({ fields: {} }));
14
+ constructor(schema) {
15
+ this.schema = schema;
18
16
  }
19
17
  /** Two extensions declaring one member refuse: the field that judges it would depend on wiring order. */
20
18
  static of(declarations) {
21
- const fields = {};
22
19
  const owners = new Map();
20
+ let schema = Schema.of({ fields: {} });
23
21
  for (const { name, state } of declarations) {
22
+ const fields = {};
24
23
  for (const [member, field] of Object.entries(state ?? {})) {
25
24
  const owner = owners.get(member);
26
25
  if (owner) {
@@ -30,8 +29,9 @@ export class StateShape {
30
29
  owners.set(member, name);
31
30
  fields[member] = optional(field);
32
31
  }
32
+ schema = schema.extend(fields);
33
33
  }
34
- return new StateShape(fields);
34
+ return new StateShape(schema);
35
35
  }
36
36
  /**
37
37
  * The members written since `entered` rebuilt by their fields, or a refusal naming the member
@@ -41,10 +41,10 @@ export class StateShape {
41
41
  const written = Object.fromEntries(Object.entries(state).filter(([member, value]) => entered[member] !== value));
42
42
  if (Object.keys(written).length === 0)
43
43
  return state;
44
- const result = this.validator.validate(written);
44
+ const result = this.schema.validate(written);
45
45
  if (result.success)
46
46
  return { ...state, ...result.data };
47
- const declared = Object.keys(this.fields);
47
+ const declared = Object.keys(this.schema.getFields());
48
48
  const errors = result.errors.map((error) => ({ ...error, path: ['state', ...error.path] }));
49
49
  throw new FougereError({
50
50
  code: ErrorCode.VALIDATION_FAILED,
@@ -1 +1 @@
1
- {"version":3,"file":"StateShape.js","sourceRoot":"","sources":["../../src/wire/StateShape.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"StateShape.js","sourceRoot":"","sources":["../../src/wire/StateShape.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAoE,MAAM,iBAAiB,CAAC;AAC7H,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAQjD;;;;;;GAMG;AACH,MAAM,OAAO,UAAU;IAGgB;IAFrC,MAAM,CAAU,KAAK,GAAG,IAAI,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC;IAElE,YAAqC,MAAkB;QAAlB,WAAM,GAAN,MAAM,CAAY;IAAG,CAAC;IAE3D,yGAAyG;IACzG,MAAM,CAAC,EAAE,CAAC,YAAyC;QACjD,MAAM,MAAM,GAAG,IAAI,GAAG,EAAkB,CAAC;QACzC,IAAI,MAAM,GAA8B,MAAM,CAAC,EAAE,CAAC,EAAE,MAAM,EAAE,EAAY,EAAE,CAAC,CAAC;QAC5E,KAAK,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,YAAY,EAAE,CAAC;YAC3C,MAAM,MAAM,GAAW,EAAE,CAAC;YAC1B,KAAK,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,IAAI,EAAE,CAAC,EAAE,CAAC;gBAC1D,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;gBACjC,IAAI,KAAK,EAAE,CAAC;oBACV,MAAM,IAAI,KAAK,CACb,YAAY,MAAM,qCAAqC,KAAK,UAAU,IAAI,MAAM;0BAC9E,mEAAmE,CACtE,CAAC;gBACJ,CAAC;gBACD,MAAM,CAAC,GAAG,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;gBACzB,MAAM,CAAC,MAAM,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;YACnC,CAAC;YACD,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QACjC,CAAC;QAED,OAAO,IAAI,UAAU,CAAC,MAAM,CAAC,CAAC;IAChC,CAAC;IAED;;;OAGG;IACH,KAAK,CACH,KAA8B,EAC9B,OAAe,EACf,SAAiB,EACjB,UAAmC,EAAE;QAErC,MAAM,OAAO,GAAG,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,KAAK,CAAC,CAAC,CAAC;QACjH,IAAI,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,KAAK,CAAC;QAEpD,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;QAC7C,IAAI,MAAM,CAAC,OAAO;YAAE,OAAO,EAAE,GAAG,KAAK,EAAE,GAAG,MAAM,CAAC,IAAI,EAAE,CAAC;QAExD,MAAM,QAAQ,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,SAAS,EAAE,CAAC,CAAC;QACtD,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,KAAK,EAAE,IAAI,EAAE,CAAC,OAAO,EAAE,GAAG,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;QAE5F,MAAM,IAAI,YAAY,CAAC;YACrB,IAAI,EAAE,SAAS,CAAC,iBAAiB;YACjC,OAAO,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,GAAG,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC;kBAChF,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,4BAA4B,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,0CAA0C,CAAC;YACtH,OAAO,EAAE,MAAM;YACf,OAAO;YACP,SAAS;SACV,CAAC,CAAC;IACL,CAAC"}
@@ -16,5 +16,10 @@ export interface TypeRef {
16
16
  undefined?: boolean;
17
17
  /** Whether this is a Promise wrapper (unwrapped in output). */
18
18
  promise?: boolean;
19
+ /** `Post['id']` — the entity whose primary key this value is, and the field holding it. */
20
+ identifies?: {
21
+ entity: string;
22
+ field: string;
23
+ };
19
24
  }
20
25
  //# sourceMappingURL=TypeRef.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"TypeRef.d.ts","sourceRoot":"","sources":["../../src/wire/TypeRef.ts"],"names":[],"mappings":"AAAA,iEAAiE;AACjE,MAAM,WAAW,OAAO;IACtB,oEAAoE;IACpE,GAAG,EAAE,MAAM,CAAC;IACZ,4DAA4D;IAC5D,IAAI,EAAE,MAAM,CAAC;IACb,sEAAsE;IACtE,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,6BAA6B;IAC7B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,+EAA+E;IAC/E,QAAQ,CAAC,EAAE,OAAO,EAAE,CAAC;IACrB,0CAA0C;IAC1C,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,mEAAmE;IACnE,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,+DAA+D;IAC/D,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB"}
1
+ {"version":3,"file":"TypeRef.d.ts","sourceRoot":"","sources":["../../src/wire/TypeRef.ts"],"names":[],"mappings":"AAAA,iEAAiE;AACjE,MAAM,WAAW,OAAO;IACtB,oEAAoE;IACpE,GAAG,EAAE,MAAM,CAAC;IACZ,4DAA4D;IAC5D,IAAI,EAAE,MAAM,CAAC;IACb,sEAAsE;IACtE,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,6BAA6B;IAC7B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,+EAA+E;IAC/E,QAAQ,CAAC,EAAE,OAAO,EAAE,CAAC;IACrB,0CAA0C;IAC1C,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,mEAAmE;IACnE,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,+DAA+D;IAC/D,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,2FAA2F;IAC3F,UAAU,CAAC,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;CAChD"}
@@ -20,10 +20,13 @@ type ParamSource = {
20
20
  | {
21
21
  kind: 'pipe';
22
22
  factName: string;
23
- } | {
23
+ }
24
+ /** `identifies` names the entity whose row the value designates — `id: Post['id']`. */
25
+ | {
24
26
  kind: 'param';
25
27
  name: string;
26
28
  coerce?: 'number' | 'boolean';
29
+ identifies?: string;
27
30
  } | {
28
31
  kind: 'input';
29
32
  } | {
@@ -1 +1 @@
1
- {"version":3,"file":"binding.d.ts","sourceRoot":"","sources":["../../src/wire/binding.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAKxC,KAAK,WAAW,GACZ;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE;AACzC,qFAAqF;GACnF;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE;AACpC;;;;GAIG;GACD;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GAClC;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,QAAQ,GAAG,SAAS,CAAA;CAAE,GAC9D;IAAE,IAAI,EAAE,OAAO,CAAA;CAAE,GACjB;IAAE,IAAI,EAAE,SAAS,CAAA;CAAE;AACrB,2EAA2E;GACzE;IAAE,IAAI,EAAE,OAAO,CAAA;CAAE,CAAC;AAEtB,UAAU,YAAY;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,WAAW,CAAC;IACpB,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,MAAM,WAAW,GAAG,YAAY,EAAE,CAAC;AAezC,qDAAqD;AACrD,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,KAAK,EAAE,EACf,kBAAkB,EAAE,GAAG,CAAC,MAAM,CAAC,GAC9B,WAAW,CAMb"}
1
+ {"version":3,"file":"binding.d.ts","sourceRoot":"","sources":["../../src/wire/binding.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAKxC,KAAK,WAAW,GACZ;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE;AACzC,qFAAqF;GACnF;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE;AACpC;;;;GAIG;GACD;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE;AACpC,uFAAuF;GACrF;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,QAAQ,GAAG,SAAS,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAA;CAAE,GACnF;IAAE,IAAI,EAAE,OAAO,CAAA;CAAE,GACjB;IAAE,IAAI,EAAE,SAAS,CAAA;CAAE;AACrB,2EAA2E;GACzE;IAAE,IAAI,EAAE,OAAO,CAAA;CAAE,CAAC;AAEtB,UAAU,YAAY;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,WAAW,CAAC;IACpB,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,MAAM,WAAW,GAAG,YAAY,EAAE,CAAC;AAezC,qDAAqD;AACrD,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,KAAK,EAAE,EACf,kBAAkB,EAAE,GAAG,CAAC,MAAM,CAAC,GAC9B,WAAW,CAMb"}
@@ -37,8 +37,10 @@ function sourceOf(param, collectorTypeNames) {
37
37
  return { kind: 'collector', typeName: typeKey };
38
38
  if (typeName === 'InvocationContext')
39
39
  return { kind: 'context' };
40
- if (PRIMITIVES.has(typeName))
41
- return { kind: 'param', name: param.name, coerce: coercionFor(typeName) };
40
+ if (PRIMITIVES.has(typeName)) {
41
+ const identifies = param.type.identifies?.entity;
42
+ return { kind: 'param', name: param.name, coerce: coercionFor(typeName), ...(identifies && { identifies }) };
43
+ }
42
44
  return { kind: 'input' };
43
45
  }
44
46
  //# sourceMappingURL=binding.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"binding.js","sourceRoot":"","sources":["../../src/wire/binding.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AA4B7C,iDAAiD;AAEjD,MAAM,UAAU,GAAG,IAAI,GAAG,CAAC,CAAC,QAAQ,EAAE,QAAQ,EAAE,SAAS,CAAC,CAAC,CAAC;AAE5D,SAAS,WAAW,CAAC,QAAgB;IACnC,IAAI,QAAQ,KAAK,QAAQ;QAAE,OAAO,QAAQ,CAAC;IAC3C,IAAI,QAAQ,KAAK,SAAS;QAAE,OAAO,SAAS,CAAC;IAE7C,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,iDAAiD;AAEjD,qDAAqD;AACrD,MAAM,UAAU,kBAAkB,CAChC,MAAe,EACf,kBAA+B;IAE/B,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;QAC5B,IAAI,EAAE,KAAK,CAAC,IAAI;QAChB,MAAM,EAAE,QAAQ,CAAC,KAAK,EAAE,kBAAkB,CAAC;QAC3C,QAAQ,EAAE,KAAK,CAAC,QAAQ,IAAI,KAAK;KAClC,CAAC,CAAC,CAAC;AACN,CAAC;AAED;;;;;GAKG;AACH,SAAS,QAAQ,CAAC,KAAY,EAAE,kBAA+B;IAC7D,MAAM,QAAQ,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC;IACjC,8FAA8F;IAC9F,qFAAqF;IACrF,sDAAsD;IACtD,MAAM,OAAO,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC;IAErC,MAAM,SAAS,GAAG,QAAQ,KAAK,MAAM,IAAI,QAAQ,KAAK,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC;IAC1G,IAAI,SAAS,EAAE,CAAC;QACd,OAAO,EAAE,IAAI,EAAE,QAAQ,KAAK,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,EAAE,QAAQ,EAAE,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;IAC1F,CAAC;IAED,IAAI,kBAAkB,CAAC,GAAG,CAAC,OAAO,CAAC;QAAE,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC;IACrF,IAAI,QAAQ,KAAK,mBAAmB;QAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC;IACjE,IAAI,UAAU,CAAC,GAAG,CAAC,QAAQ,CAAC;QAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,WAAW,CAAC,QAAQ,CAAC,EAAE,CAAC;IAExG,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;AAC3B,CAAC"}
1
+ {"version":3,"file":"binding.js","sourceRoot":"","sources":["../../src/wire/binding.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AA6B7C,iDAAiD;AAEjD,MAAM,UAAU,GAAG,IAAI,GAAG,CAAC,CAAC,QAAQ,EAAE,QAAQ,EAAE,SAAS,CAAC,CAAC,CAAC;AAE5D,SAAS,WAAW,CAAC,QAAgB;IACnC,IAAI,QAAQ,KAAK,QAAQ;QAAE,OAAO,QAAQ,CAAC;IAC3C,IAAI,QAAQ,KAAK,SAAS;QAAE,OAAO,SAAS,CAAC;IAE7C,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,iDAAiD;AAEjD,qDAAqD;AACrD,MAAM,UAAU,kBAAkB,CAChC,MAAe,EACf,kBAA+B;IAE/B,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;QAC5B,IAAI,EAAE,KAAK,CAAC,IAAI;QAChB,MAAM,EAAE,QAAQ,CAAC,KAAK,EAAE,kBAAkB,CAAC;QAC3C,QAAQ,EAAE,KAAK,CAAC,QAAQ,IAAI,KAAK;KAClC,CAAC,CAAC,CAAC;AACN,CAAC;AAED;;;;;GAKG;AACH,SAAS,QAAQ,CAAC,KAAY,EAAE,kBAA+B;IAC7D,MAAM,QAAQ,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC;IACjC,8FAA8F;IAC9F,qFAAqF;IACrF,sDAAsD;IACtD,MAAM,OAAO,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC;IAErC,MAAM,SAAS,GAAG,QAAQ,KAAK,MAAM,IAAI,QAAQ,KAAK,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC;IAC1G,IAAI,SAAS,EAAE,CAAC;QACd,OAAO,EAAE,IAAI,EAAE,QAAQ,KAAK,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,EAAE,QAAQ,EAAE,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;IAC1F,CAAC;IAED,IAAI,kBAAkB,CAAC,GAAG,CAAC,OAAO,CAAC;QAAE,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC;IACrF,IAAI,QAAQ,KAAK,mBAAmB;QAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC;IACjE,IAAI,UAAU,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC7B,MAAM,UAAU,GAAG,KAAK,CAAC,IAAI,CAAC,UAAU,EAAE,MAAM,CAAC;QAEjD,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,WAAW,CAAC,QAAQ,CAAC,EAAE,GAAG,CAAC,UAAU,IAAI,EAAE,UAAU,EAAE,CAAC,EAAE,CAAC;IAC/G,CAAC;IAED,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;AAC3B,CAAC"}
@@ -1,23 +1,17 @@
1
1
  import type { SchemaDescriptor } from '@fougere/schema';
2
- import type { ErrorCode } from '../ErrorCode.js';
2
+ import type { OperationContract } from '../OperationContract.js';
3
+ import type { OperationKind } from '../OperationKind.js';
3
4
  /**
4
5
  * One operation, as a stranger meets it — its name, what it is for, what it takes and whether it
5
6
  * reads or writes.
6
7
  */
7
- export interface CardOp {
8
+ export interface CardOp extends Pick<OperationContract, 'description' | 'cardinality' | 'errors'> {
8
9
  name: string;
9
- /** The author's own doc sentence, when the method carries one. */
10
- description?: string;
11
10
  /** JSON Schema of what it accepts, when the contract names a view. */
12
11
  input?: SchemaDescriptor;
13
12
  /** JSON Schema of what it emits, when the contract names one. */
14
13
  output?: SchemaDescriptor;
15
- /** `query` reads, `command` writes — the same call REST turns into GET vs POST. */
16
- kind: 'query' | 'command';
17
- /** How much `output` describes. */
18
- cardinality?: 'one' | 'maybe' | 'many' | 'page' | 'none';
19
- /** What this op can REFUSE, beyond what `kind` and `input` already imply. */
20
- errors?: ErrorCode[];
14
+ kind: OperationKind;
21
15
  /** The fact this op is handed when it is announced — its `Fact<T>`, which IS the subscription. */
22
16
  listens?: string;
23
17
  }
@@ -1 +1 @@
1
- {"version":3,"file":"CardOp.d.ts","sourceRoot":"","sources":["../../../src/wire/card/CardOp.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACxD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAEjD;;;GAGG;AACH,MAAM,WAAW,MAAM;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,kEAAkE;IAClE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,sEAAsE;IACtE,KAAK,CAAC,EAAE,gBAAgB,CAAC;IACzB,iEAAiE;IACjE,MAAM,CAAC,EAAE,gBAAgB,CAAC;IAC1B,mFAAmF;IACnF,IAAI,EAAE,OAAO,GAAG,SAAS,CAAC;IAC1B,mCAAmC;IACnC,WAAW,CAAC,EAAE,KAAK,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC;IACzD,6EAA6E;IAC7E,MAAM,CAAC,EAAE,SAAS,EAAE,CAAC;IACrB,kGAAkG;IAClG,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB"}
1
+ {"version":3,"file":"CardOp.d.ts","sourceRoot":"","sources":["../../../src/wire/card/CardOp.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACxD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AACjE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEzD;;;GAGG;AACH,MAAM,WAAW,MAAO,SAAQ,IAAI,CAAC,iBAAiB,EAAE,aAAa,GAAG,aAAa,GAAG,QAAQ,CAAC;IAC/F,IAAI,EAAE,MAAM,CAAC;IACb,sEAAsE;IACtE,KAAK,CAAC,EAAE,gBAAgB,CAAC;IACzB,iEAAiE;IACjE,MAAM,CAAC,EAAE,gBAAgB,CAAC;IAC1B,IAAI,EAAE,aAAa,CAAC;IACpB,kGAAkG;IAClG,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB"}
@@ -14,9 +14,10 @@
14
14
  * Documented: [observability](https://fougere.dev/docs/infra/observability).
15
15
  */
16
16
  import { ErrorCode } from './ErrorCode.js';
17
+ import type { OperationKind } from './OperationKind.js';
17
18
  /** The little of an operation this reads — so a card op and an effective one both fit. */
18
19
  export interface Refusable {
19
- kind?: 'query' | 'command';
20
+ kind?: OperationKind;
20
21
  input?: unknown;
21
22
  errors?: readonly string[];
22
23
  /** Absent on a card op read from a frond that answers in this process. */
@@ -1 +1 @@
1
- {"version":3,"file":"refusals.d.ts","sourceRoot":"","sources":["../../src/wire/refusals.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAE3C,0FAA0F;AAC1F,MAAM,WAAW,SAAS;IACxB,IAAI,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAC3B,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,MAAM,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3B,0EAA0E;IAC1E,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAmBD;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,EAAE,EAAE,SAAS,GAAG,SAAS,EAAE,CAIrD"}
1
+ {"version":3,"file":"refusals.d.ts","sourceRoot":"","sources":["../../src/wire/refusals.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAExD,0FAA0F;AAC1F,MAAM,WAAW,SAAS;IACxB,IAAI,CAAC,EAAE,aAAa,CAAC;IACrB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,MAAM,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3B,0EAA0E;IAC1E,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAmBD;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,EAAE,EAAE,SAAS,GAAG,SAAS,EAAE,CAIrD"}
@@ -1 +1 @@
1
- {"version":3,"file":"refusals.js","sourceRoot":"","sources":["../../src/wire/refusals.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAW3C;;;;GAIG;AACH,MAAM,OAAO,GAA4D;IACvE,qFAAqF;IACrF,EAAE,IAAI,EAAE,SAAS,CAAC,mBAAmB,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC,IAAI,EAAE;IACzD,sFAAsF;IACtF,EAAE,IAAI,EAAE,SAAS,CAAC,iBAAiB,EAAE,IAAI,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,KAAK,KAAK,SAAS,EAAE;IAC3E,oFAAoF;IACpF,EAAE,IAAI,EAAE,SAAS,CAAC,WAAW,EAAE,IAAI,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,IAAI,KAAK,SAAS,EAAE;IACpE,0FAA0F;IAC1F,EAAE,IAAI,EAAE,SAAS,CAAC,eAAe,EAAE,IAAI,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,MAAM,KAAK,IAAI,EAAE;IACrE,EAAE,IAAI,EAAE,SAAS,CAAC,WAAW,EAAE,IAAI,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,MAAM,KAAK,IAAI,EAAE;CAClE,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,UAAU,UAAU,CAAC,EAAa;IACtC,MAAM,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAE7E,OAAO,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,MAAM,IAAI,EAAE,CAAgB,EAAE,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;AAChF,CAAC"}
1
+ {"version":3,"file":"refusals.js","sourceRoot":"","sources":["../../src/wire/refusals.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAY3C;;;;GAIG;AACH,MAAM,OAAO,GAA4D;IACvE,qFAAqF;IACrF,EAAE,IAAI,EAAE,SAAS,CAAC,mBAAmB,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC,IAAI,EAAE;IACzD,sFAAsF;IACtF,EAAE,IAAI,EAAE,SAAS,CAAC,iBAAiB,EAAE,IAAI,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,KAAK,KAAK,SAAS,EAAE;IAC3E,oFAAoF;IACpF,EAAE,IAAI,EAAE,SAAS,CAAC,WAAW,EAAE,IAAI,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,IAAI,KAAK,SAAS,EAAE;IACpE,0FAA0F;IAC1F,EAAE,IAAI,EAAE,SAAS,CAAC,eAAe,EAAE,IAAI,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,MAAM,KAAK,IAAI,EAAE;IACrE,EAAE,IAAI,EAAE,SAAS,CAAC,WAAW,EAAE,IAAI,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,MAAM,KAAK,IAAI,EAAE;CAClE,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,UAAU,UAAU,CAAC,EAAa;IACtC,MAAM,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAE7E,OAAO,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,MAAM,IAAI,EAAE,CAAgB,EAAE,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;AAChF,CAAC"}
@@ -1,7 +1,8 @@
1
+ import type { Placement } from '../Placement.js';
1
2
  /** A frond the config knows of, and where it says to reach it. */
2
3
  export interface DeclaredFrond {
3
4
  frond: string;
4
- placement: 'local' | 'remote';
5
+ placement: Placement;
5
6
  /**
6
7
  * Host and port of the address `remotes:` names, absent when the frond runs here. Never the
7
8
  * whole address: a declared one may carry credentials, and this answer leaves the process.
@@ -1 +1 @@
1
- {"version":3,"file":"DeclaredFrond.d.ts","sourceRoot":"","sources":["../../../src/wire/topology/DeclaredFrond.ts"],"names":[],"mappings":"AAAA,kEAAkE;AAClE,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,OAAO,GAAG,QAAQ,CAAC;IAC9B;;;OAGG;IACH,EAAE,CAAC,EAAE,MAAM,CAAC;CACb"}
1
+ {"version":3,"file":"DeclaredFrond.d.ts","sourceRoot":"","sources":["../../../src/wire/topology/DeclaredFrond.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAEjD,kEAAkE;AAClE,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,SAAS,CAAC;IACrB;;;OAGG;IACH,EAAE,CAAC,EAAE,MAAM,CAAC;CACb"}
@@ -1,7 +1,8 @@
1
+ import type { Placement } from '../Placement.js';
1
2
  /** A frond this process knows about, and whether it runs here. */
2
3
  export interface FrondPlacement {
3
4
  frond: string;
4
- placement: 'local' | 'remote';
5
+ placement: Placement;
5
6
  entities: number;
6
7
  facades: number;
7
8
  }
@@ -1 +1 @@
1
- {"version":3,"file":"FrondPlacement.d.ts","sourceRoot":"","sources":["../../../src/wire/topology/FrondPlacement.ts"],"names":[],"mappings":"AAAA,kEAAkE;AAClE,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,OAAO,GAAG,QAAQ,CAAC;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;CACjB"}
1
+ {"version":3,"file":"FrondPlacement.d.ts","sourceRoot":"","sources":["../../../src/wire/topology/FrondPlacement.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAEjD,kEAAkE;AAClE,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,SAAS,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;CACjB"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fougere/core",
3
- "version": "0.17.4-alpha.0",
3
+ "version": "0.18.0-alpha.0",
4
4
  "description": "The core — scan, call contract, façades, remote stand-ins.",
5
5
  "keywords": [
6
6
  "fougere",
@@ -61,8 +61,8 @@
61
61
  "tsconfig"
62
62
  ],
63
63
  "dependencies": {
64
- "@fougere/container": "0.17.4-alpha.0",
65
- "@fougere/schema": "0.17.4-alpha.0",
64
+ "@fougere/container": "0.18.0-alpha.0",
65
+ "@fougere/schema": "0.18.0-alpha.0",
66
66
  "dequal": "^2.0.3"
67
67
  },
68
68
  "devDependencies": {
package/src/Declared.ts CHANGED
@@ -1,11 +1,7 @@
1
1
  import type { AbstractCtor } from './Ctor.js';
2
2
  import type { DeclaredSubject } from './DeclaredSubject.js';
3
+ import type { ProviderEntry } from './descriptor/ProviderEntry.js';
3
4
 
4
5
  /** A class on its own — a port, which is abstract, included — or a class with what it asks for. */
5
- export type Declared = AbstractCtor | (Omit<DeclaredSubject, 'ctor'> & {
6
- ctor: AbstractCtor;
7
- /** The container key, when the class's own name cannot be trusted to survive a build. */
8
- name?: string;
9
- /** The class is `abstract` — a port and never a realization. The scan writes it; TypeScript erases it. */
10
- abstract?: true;
11
- });
6
+ export type Declared = AbstractCtor
7
+ | (Omit<DeclaredSubject, 'ctor'> & { ctor: AbstractCtor } & Partial<Pick<ProviderEntry, 'name' | 'abstract'>>);
@@ -1,18 +1,13 @@
1
1
  import type { OperationContract } from './wire/OperationContract.js';
2
2
  import type { DeclaredSubject } from './DeclaredSubject.js';
3
+ import type { HandlerEntry } from './descriptor/HandlerEntry.js';
3
4
 
4
5
  /** A handler, and the surface it answers on when it is not the default one. */
5
- export interface DeclaredHandler extends DeclaredSubject {
6
- /**
7
- * The scan reads this from the directory (`handlers/public/`), so a statement has to say it: two
8
- * handlers over one entity collide on their address otherwise, and the refusal names the same
9
- * route twice.
10
- */
11
- surface?: string;
6
+ export type DeclaredHandler = DeclaredSubject & Partial<Pick<HandlerEntry, 'surface'>> & {
12
7
  /**
13
8
  * What each method takes and answers — read from SOURCE by the scan, and unreachable
14
9
  * from a class at runtime. A prefab declares its own (`Crud.__ops`) and needs nothing
15
10
  * here; a method someone wrote does, or the route it should serve does not exist.
16
11
  */
17
12
  operations?: ReadonlyMap<string, OperationContract> | Record<string, OperationContract>;
18
- }
13
+ };
@@ -1,6 +1,4 @@
1
- import type { Ctor } from './Ctor.js';
1
+ import type { Subject } from './descriptor/Subject.js';
2
+
2
3
  /** What a subject needs beyond its class, when its constructor names a frame or a port. */
3
- export interface DeclaredSubject {
4
- ctor: Ctor;
5
- deps?: string[];
6
- }
4
+ export type DeclaredSubject = Pick<Subject, 'ctor'> & Partial<Pick<Subject, 'deps'>>;
@@ -4,6 +4,7 @@ import { type OperationContract } from './wire/OperationContract.js';
4
4
  import { type OperationKind } from './wire/OperationKind.js';
5
5
  import type { EffectiveParameter } from './EffectiveParameter.js';
6
6
  import type { EffectiveCollector } from './EffectiveCollector.js';
7
+ import type { Placement } from './wire/Placement.js';
7
8
 
8
9
  export interface EffectiveOperation extends OperationContract {
9
10
  /** Stable qualified identity: `blog/public/Post.publish`. */
@@ -30,11 +31,11 @@ export interface EffectiveOperation extends OperationContract {
30
31
  parameters: EffectiveParameter[];
31
32
  collectors: EffectiveCollector[];
32
33
  contexts: string[];
33
- placement: {
34
- frond: string;
35
- runtime: 'local' | 'remote';
36
- remote?: string;
37
- };
34
+ /** The frond that answers it, and where — `placement` says where it ANSWERS, `reach` where it GOES. */
35
+ frond: string;
36
+ placement: Placement;
37
+ /** The address it is reached at, when it answers a process away. */
38
+ remote?: string;
38
39
  /**
39
40
  * What running this op reaches, and how much of that crosses a process.
40
41
  *
@@ -48,7 +49,7 @@ export interface EffectiveOperation extends OperationContract {
48
49
  * saying otherwise would dress a structural fact as a precise effect.
49
50
  */
50
51
  reach: {
51
- fronds: { frond: string; runtime: 'local' | 'remote' }[];
52
+ fronds: { frond: string; placement: Placement }[];
52
53
  /** How many of them are a process away — zero when everything it reaches runs here. */
53
54
  hops: number;
54
55
  };
@@ -18,8 +18,10 @@ import type { EffectiveParameter } from './EffectiveParameter.js';
18
18
  import type { EffectiveOperation } from './EffectiveOperation.js';
19
19
  import type { EffectiveOperationsMap } from './EffectiveOperationsMap.js';
20
20
  import type { EffectiveOperationOptions } from './EffectiveOperationOptions.js';
21
+ import type { Placement } from './wire/Placement.js';
22
+ import type { Binding } from './Binding.js';
23
+ import { opsOf } from './prefab/opsOf.js';
21
24
 
22
- type Binding = BindingPlan[number];
23
25
 
24
26
  /** A contract whose parameters are all bound. */
25
27
  type BoundContract = OperationContract & { binding: BindingPlan };
@@ -39,9 +41,9 @@ function reachOf(
39
41
  ): EffectiveOperation['reach'] {
40
42
  const fronds = reachedBy(deps, from, index)
41
43
  .sort()
42
- .map((frond) => ({ frond, runtime: (remotes[frond] ? 'remote' : 'local') as 'local' | 'remote' }));
44
+ .map((frond) => ({ frond, placement: (remotes[frond] ? 'remote' : 'local') as Placement }));
43
45
 
44
- return { fronds, hops: fronds.filter((one) => one.runtime === 'remote').length };
46
+ return { fronds, hops: fronds.filter((one) => one.placement === 'remote').length };
45
47
  }
46
48
 
47
49
 
@@ -193,7 +195,9 @@ function resolveOperation(resolving: Resolving, name: string, rawContract: Opera
193
195
  contexts: parameters
194
196
  .filter((parameter) => parameter.binding.source.kind === 'context')
195
197
  .map((parameter) => parameter.name),
196
- placement: { frond: frond.name, runtime: remote ? 'remote' : 'local', ...(remote ? { remote } : {}) },
198
+ frond: frond.name,
199
+ placement: remote ? 'remote' : 'local',
200
+ ...(remote ? { remote } : {}),
197
201
  reach: reachOf(handler.deps, frond.name, served, options.remotes ?? {}),
198
202
  exposure: {
199
203
  surfaces: servedSurfaces(frond, handler).map((surface) => surface ?? 'default'),
@@ -388,7 +392,7 @@ function validateProvenance(
388
392
  diagnostics: Diagnostic[],
389
393
  ): boolean {
390
394
  let valid = true;
391
- const declared = (handler.ctor as { __ops?: Record<string, OperationContract> }).__ops?.[name];
395
+ const declared = opsOf(handler.ctor)[name];
392
396
  const explicitBinding = declared?.binding !== undefined
393
397
  || handler.operations.get(name)?.binding !== undefined
394
398
  || frond.operationsOverrides?.[name]?.binding !== undefined;
@@ -519,7 +523,7 @@ function implementationOf(
519
523
  const method = override?.method ?? name;
520
524
  const declared = implementation.operations.has(method)
521
525
  || typeof (implementation.ctor as { prototype?: Record<string, unknown> }).prototype?.[method] === 'function'
522
- || method in ((implementation.ctor as { __ops?: Record<string, unknown> }).__ops ?? {});
526
+ || method in opsOf(implementation.ctor);
523
527
  if (!declared) {
524
528
  diagnostics.push({
525
529
  severity: 'blocking',
@@ -592,7 +596,7 @@ function resolveContracts(
592
596
  overrides: FrondDescriptor['operationsOverrides'],
593
597
  collectorTypeNames: Set<string>,
594
598
  ): OperationsMap {
595
- const declared = (handler.ctor as { __ops?: Record<string, OperationContract> }).__ops ?? {};
599
+ const declared = opsOf(handler.ctor);
596
600
  const contracts: OperationsMap = new Map(Object.entries(declared));
597
601
 
598
602
  for (const [opName, scanned] of handler.operations) {
@@ -10,8 +10,6 @@ export interface FrondConfig {
10
10
  reads?: NameOf<'entity'>[];
11
11
  /** Per-surface entity lists. Overrides default deduction for each named surface. */
12
12
  surfaces?: Record<string, NameOf<'entity'>[]>;
13
- /** Interface → implementation bindings for DI (e.g. { Database: 'SqliteDatabase' }). */
14
- bindings?: Record<string, NameOf<'provider'>>;
15
13
  /**
16
14
  * The ops that FINISH a fact, in the order they run — by class name, keyed by fact.
17
15
  *
@@ -15,9 +15,10 @@ import type { OperationContract } from './wire/OperationContract.js';
15
15
  import type { DeclaredSubject } from './DeclaredSubject.js';
16
16
  import type { DeclaredHandler } from './DeclaredHandler.js';
17
17
  import type { Declared } from './Declared.js';
18
+ import { opsOf } from './prefab/opsOf.js';
19
+ import type { Ctor } from './Ctor.js';
20
+ import type { FrondConfig } from './FrondConfig.js';
18
21
 
19
- /** A class, as a declaration hands it over: the constructor itself. */
20
- type Ctor = new (...args: never[]) => unknown;
21
22
 
22
23
  const ctorOf = (d: Declared): Ctor => (typeof d === 'function' ? d : d.ctor) as Ctor;
23
24
 
@@ -47,7 +48,7 @@ const depsOf = (d: Declared): string[] => (typeof d === 'function' ? [] : d.deps
47
48
  */
48
49
  function statedOperations(h: Ctor | DeclaredHandler): Map<string, OperationContract> {
49
50
  const declared = typeof h === 'function' ? undefined : h.operations;
50
- const own = (ctorOf(h) as { __ops?: Record<string, OperationContract> }).__ops ?? {};
51
+ const own = opsOf(ctorOf(h));
51
52
  const merged = new Map<string, OperationContract>(Object.entries(own));
52
53
  for (const [op, contract] of declared instanceof Map ? declared : Object.entries(declared ?? {})) {
53
54
  merged.set(op, contract as OperationContract);
@@ -57,7 +58,7 @@ function statedOperations(h: Ctor | DeclaredHandler): Map<string, OperationContr
57
58
  }
58
59
 
59
60
  /** What a declaration states about one frond. Everything else is derived from the classes. */
60
- export interface FrondDeclaration {
61
+ export interface FrondDeclaration extends Pick<FrondConfig, 'pipes' | 'surfaces'> {
61
62
  entities?: SchemaView[];
62
63
  handlers?: (Ctor | DeclaredHandler)[];
63
64
  presenters?: (Ctor | DeclaredSubject)[];
@@ -66,10 +67,6 @@ export interface FrondDeclaration {
66
67
  seeds?: { entityName: string; data: SeedEntry['data'] }[];
67
68
  /** What runs around this frond's operations, and around those of the fronds under it. */
68
69
  middlewares?: (Ctor | DeclaredSubject)[];
69
- /** The ops that finish a fact, in order — the same key `frond.config.ts` states. */
70
- pipes?: Record<string, string[]>;
71
- /** Per-surface entity lists — the same key `frond.config.ts` states. */
72
- surfaces?: Record<string, string[]>;
73
70
  /** The import scope this frond answers under. Defaults to the conventional one. */
74
71
  scope?: string;
75
72
  /** What `frond.config.ts` states — the third producer of an operation contract. */
@@ -1,10 +1,12 @@
1
- import type { EntityConstructor, SchemaView } from '@fougere/schema';
1
+ import type { SchemaView } from '@fougere/schema';
2
+ import type { Ctor } from './Ctor.js';
2
3
  import type { BindingPlan } from './wire/binding.js';
4
+ import type { OperationKind } from './wire/OperationKind.js';
3
5
 
4
6
  /** Per-operation override. */
5
7
  export interface OperationOverride {
6
8
  /** Force operation kind, over the naming convention (`isReadOp`). */
7
- kind?: 'query' | 'command';
9
+ kind?: OperationKind;
8
10
  /** The GraphQL root field this op answers to. */
9
11
  graphql?: string;
10
12
  /**
@@ -17,7 +19,7 @@ export interface OperationOverride {
17
19
  * Handler class to delegate to (overrides the default `{Entity}Handler` lookup).
18
20
  * Class name is used to resolve from DI. E.g. `ArchiveHandler` → `app.resolve('ArchiveHandler')`.
19
21
  */
20
- handler?: EntityConstructor;
22
+ handler?: Ctor;
21
23
  /** Method name on `handler` (defaults to the operation name). */
22
24
  method?: string;
23
25
  /** CASL ability check (e.g. */
package/src/SourceView.ts CHANGED
@@ -1,5 +1,23 @@
1
- /** The app as ONE source sees it. */
1
+ import type { SchemaView } from '@fougere/schema';
2
+ import type { EntityEntry } from './descriptor/EntityEntry.js';
3
+
4
+ /** The app as ONE source sees it: the entities it holds, by the name the scan wrote down. */
2
5
  export interface SourceView {
3
- fronds: readonly { readonly name: string; readonly entities: readonly { readonly name: string }[] }[];
4
- elsewhere: readonly string[];
6
+ entities: ReadonlyMap<string, SchemaView>;
7
+ /** The names another source holds — a reference to one gets a column and no key. Absent: all here. */
8
+ elsewhere?: readonly string[];
9
+ }
10
+
11
+ /** What a source holds of these fronds — every entity, or the ones `holds` keeps and the rest named. */
12
+ export function sourceViewOf(
13
+ fronds: readonly { readonly entities: readonly Pick<EntityEntry, 'name' | 'entityClass'>[] }[],
14
+ holds?: (name: string) => boolean,
15
+ ): SourceView {
16
+ const all = new Map(fronds.flatMap((frond) => frond.entities.map((entry) => [entry.name, entry.entityClass] as const)));
17
+ if (!holds) return { entities: all };
18
+
19
+ return {
20
+ entities: new Map([...all].filter(([name]) => holds(name))),
21
+ elsewhere: [...all.keys()].filter((name) => !holds(name)).sort(),
22
+ };
5
23
  }
@@ -574,7 +574,7 @@ function answerRemotes(
574
574
  { container, routeRegistry, dispatcher, getMiddlewares, options, state }: Pick<Assembly, 'container' | 'routeRegistry' | 'dispatcher' | 'getMiddlewares' | 'options' | 'state'>,
575
575
  remoteRouter: RemoteRouter,
576
576
  fronds: Fronds,
577
- ): void {
577
+ ): (name: string) => boolean {
578
578
  // Only for what another process may serve: an entity a frond hosts HERE and gives no handler
579
579
  // is served by nobody, and a stand-in for it had GraphQL project an entity with no operation.
580
580
  const servedElsewhere = (address: string): boolean => {
@@ -582,9 +582,8 @@ function answerRemotes(
582
582
 
583
583
  return owner === undefined || owner.name in (options.remotes ?? {});
584
584
  };
585
- container.setFallback((name) => (isFacadeKey(name) && servedElsewhere(addressOf(name))
586
- ? facadeOperations(dispatcher, addressOf(name))
587
- : undefined));
585
+ const answeredElsewhere = (name: string): boolean => isFacadeKey(name) && servedElsewhere(addressOf(name));
586
+ container.setFallback((name) => (answeredElsewhere(name) ? facadeOperations(dispatcher, addressOf(name)) : undefined));
588
587
 
589
588
  const remoteFacades = new Map<string, Record<string, Function>>();
590
589
  routeRegistry.addResolver(remoteRoutes((address) => {
@@ -595,6 +594,8 @@ function answerRemotes(
595
594
 
596
595
  return facade;
597
596
  }));
597
+
598
+ return answeredElsewhere;
598
599
  }
599
600
 
600
601
  /** Refused rather than replaced: two declarations of one name would make the answer depend on wiring order. */
@@ -700,7 +701,7 @@ export async function createApp(options: CreateAppOptions): Promise<App> {
700
701
  refuseWhatDoesNotHold(refused);
701
702
  markLineCarriers(emissions);
702
703
 
703
- if (remoteRouter) answerRemotes(assembly, remoteRouter, fronds);
704
+ const answeredElsewhere = remoteRouter ? answerRemotes(assembly, remoteRouter, fronds) : () => false;
704
705
 
705
706
  const { resolve, schemaFor, facadeFor, operationsFor, presenterFor } = readings({
706
707
  container, fronds, remoteRouter, localDispatcher, routeRegistry, effectiveByKey, log,
@@ -741,7 +742,7 @@ export async function createApp(options: CreateAppOptions): Promise<App> {
741
742
 
742
743
  built = app;
743
744
  await appLifecycle.up(app);
744
- refuseWhatDoesNotHold(unregistered(container, fronds));
745
+ refuseWhatDoesNotHold(unregistered(container, fronds, answeredElsewhere));
745
746
  stopAnnouncing = announceLines(emissions, container, carry);
746
747
 
747
748
  return app;
@@ -1,42 +1,40 @@
1
1
  import type { Container } from '@fougere/container';
2
2
  import type { Diagnostic } from '../diagnostic.js';
3
3
  import type { FrondDescriptor } from '../descriptor/FrondDescriptor.js';
4
-
5
- type Declared = { frond: string; filePath: string };
6
-
7
- /** Every class a frond hands the container, by identity — the one key a bundler cannot rename. */
8
- function declaredBy(fronds: readonly FrondDescriptor[]): Map<Function, Declared> {
9
- const declared = new Map<Function, Declared>();
10
- for (const frond of fronds) {
11
- const entries = [...frond.handlers, ...frond.providers, ...frond.presenters, ...frond.collectors, ...frond.middlewares];
12
- for (const { ctor, filePath } of entries) declared.set(ctor, { frond: frond.name, filePath });
13
- }
14
-
15
- return declared;
16
- }
4
+ import { SEAMS } from './ports.js';
17
5
 
18
6
  /**
19
7
  * What a constructor asks for and nothing where it is built answers — refused at boot rather than
20
- * at its first call, once the extensions have risen, since `up` registers too.
8
+ * at its first call, once the extensions have risen, since `up` registers too. Read off what each
9
+ * class DECLARES, in the scope of the frond that declares it. A seam is answered by the link that
10
+ * wraps it, and a facade another process serves by the stand-in `answeredElsewhere` names.
21
11
  */
22
- export function unregistered(container: Container, fronds: readonly FrondDescriptor[]): Diagnostic[] {
23
- const declared = declaredBy(fronds);
24
- const seen = new Set<string>();
25
-
26
- return container.unresolved().flatMap(({ ctor, missing }) => {
27
- const subject = ctor.name;
28
- if (seen.has(`${subject}→${missing}`)) return [];
29
- seen.add(`${subject}→${missing}`);
30
- const where = declared.get(ctor);
12
+ export function unregistered(
13
+ container: Container,
14
+ fronds: readonly FrondDescriptor[],
15
+ answeredElsewhere: (name: string) => boolean,
16
+ ): Diagnostic[] {
17
+ const refused = new Map<string, Diagnostic>();
18
+ for (const frond of fronds) {
19
+ if (!container.has(`frond:${frond.name}`)) continue;
20
+ const scope = container.resolve<Container>(`frond:${frond.name}`);
21
+ const declared = [...frond.handlers, ...frond.providers, ...frond.presenters, ...frond.collectors, ...frond.middlewares];
22
+ for (const { ctor, deps, filePath } of declared) {
23
+ for (const missing of deps) {
24
+ if (scope.has(missing) || SEAMS.has(missing) || answeredElsewhere(missing)) continue;
25
+ if (refused.has(`${ctor.name}→${missing}`)) continue;
26
+ refused.set(`${ctor.name}→${missing}`, {
27
+ severity: 'blocking',
28
+ code: 'dependency-unregistered',
29
+ frond: frond.name,
30
+ subject: ctor.name,
31
+ filePath,
32
+ message: `${ctor.name} asks for ${missing}, and nothing in frond '${frond.name}' or above it answers that name. `
33
+ + `A class of that name in a convention directory, or one extending it, would.`,
34
+ });
35
+ }
36
+ }
37
+ }
31
38
 
32
- return [{
33
- severity: 'blocking' as const,
34
- code: 'dependency-unregistered',
35
- ...(where ? { frond: where.frond } : {}),
36
- subject,
37
- filePath: where?.filePath ?? '',
38
- message: `${subject} asks for ${missing}, and nothing ${where ? `in frond '${where.frond}' or above it ` : ''}answers that name. `
39
- + `A class of that name in a convention directory, or one extending it, would.`,
40
- }];
41
- });
39
+ return [...refused.values()];
42
40
  }