Statum.vala 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137
  1. using Inversion;
  2. using Invercargill.Expressions;
  3. using Astralis;
  4. namespace Statum {
  5. /**
  6. * Inversion module that registers the Statum runtime services and framework
  7. * endpoints.
  8. *
  9. * Register this module with the application container (after which a
  10. * {@link StatumConfigurator} binds pages/entrypoints/resources) to wire up
  11. * the {@link StateService}, signing/encryption providers, realtime channel,
  12. * entrypoint route table, held-slot resolver and the `/_statum/*` endpoints.
  13. */
  14. public class StatumModule : Object, Module {
  15. public void register_components(Container container) throws Error {
  16. container.register_singleton<Cryptography.SigningProvider>();
  17. container.register_singleton<Cryptography.EncryptionProvider>();
  18. // Enable predicate member-access on Properties (slot public/private
  19. // data) for the runtime `pstm-if`/`pstm-guard` evaluator.
  20. Invercargill.Expressions.TypeAccessorRegistry.get_instance()
  21. .register_property_accessor(new StatumPropertiesAccessorFactory());
  22. container.register_singleton<StateService>();
  23. container.register_singleton<HeldSlotResolver>();
  24. container.register_singleton<EntrypointRouteTable>();
  25. container.register_singleton<ActionRegistry>();
  26. // The realtime channel endpoint is the singleton ChannelService.
  27. container.register_singleton<ChannelEndpoint>()
  28. .as<ChannelService>()
  29. .as<Endpoint>()
  30. .with_metadata<EndpointRoute>(new EndpointRoute("/_statum/channel"));
  31. container.register_scoped<ChannelSubscriptionEndpoint>()
  32. .as<Endpoint>()
  33. .with_metadata<EndpointRoute>(new EndpointRoute("/_statum/channel/{id}", Method.PATCH));
  34. container.register_scoped<EntrypointEndpoint>()
  35. .as<Endpoint>()
  36. .with_metadata<EndpointRoute>(new EndpointRoute("/_statum/entrypoint"));
  37. container.register_scoped<SlotPostEndpoint>()
  38. .as<Endpoint>()
  39. .with_metadata<EndpointRoute>(new EndpointRoute("/_statum/slots", Method.POST));
  40. // The single action endpoint resolves /_statum/action/{guid} to the
  41. // action type via ActionRegistry. Registered for all common verbs so
  42. // an action may be authored with whichever method suits it.
  43. container.register_scoped<ActionEndpoint>()
  44. .as<Endpoint>()
  45. .with_metadata<EndpointRoute>(new EndpointRoute("/_statum/action/{guid}",
  46. Method.GET, Method.POST, Method.PUT, Method.PATCH, Method.DELETE));
  47. container.register_scoped<ResourceEndpoint>()
  48. .as<Endpoint>()
  49. .with_metadata<EndpointRoute>(new EndpointRoute("/_statum/resource/{name}"));
  50. // Embed the Statum client scripts as default resources, served from
  51. // /_statum/resource/statum.js and /_statum/resource/statum-worker.js
  52. // (precompressed at build time). Apps get them for free and may add
  53. // their own resources via StatumConfigurator.add_resource.
  54. container.register_startup<ClientScript>().as<StatumResource>();
  55. container.register_startup<ClientWorker>().as<StatumResource>();
  56. }
  57. }
  58. /**
  59. * Binds application pages, entrypoints and resources into the container.
  60. *
  61. * Usage mirrors Spry's {@link Spry.SpryConfigurator}:
  62. *
  63. * ```
  64. * var statum = application.configure_with<StatumConfigurator>();
  65. * statum.add_entrypoint_page<HomePage, HomeEntrypoint>(new EndpointRoute("/"));
  66. * statum.add_page<AboutPage>(new EndpointRoute("/about"));
  67. * statum.add_resource<LogoResource>();
  68. * ```
  69. *
  70. * {@link add_entrypoint_page} registers the generated {@link StatumPage}
  71. * (served at its URI) and adds the entrypoint to the {@link EntrypointRouteTable}
  72. * so `/_statum/entrypoint?uri=…` dispatches to it. The page route is supplied
  73. * explicitly (mirroring Spry) rather than read reflectively from the class.
  74. */
  75. public class StatumConfigurator : Object {
  76. private Container container = inject<Container>();
  77. private EntrypointRouteTable route_table = inject<EntrypointRouteTable>();
  78. private ActionRegistry action_registry = inject<ActionRegistry>();
  79. /**
  80. * Registers a static page (no entrypoint) served at `route`.
  81. */
  82. public void add_page<TPage>(EndpointRoute route) {
  83. container.register_scoped<TPage>()
  84. .as<Endpoint>()
  85. .with_metadata<EndpointRoute>(route);
  86. }
  87. /**
  88. * Registers a page served at `route` AND binds `TEntrypoint` to that route
  89. * in the entrypoint table (so `/_statum/entrypoint?uri=route` dispatches to
  90. * it).
  91. *
  92. * (Named distinctly from {@link add_page} because Vala does not permit
  93. * methods that overload on generic arity.)
  94. */
  95. public void add_entrypoint_page<TPage, TEntrypoint>(EndpointRoute route) {
  96. add_page<TPage>(route);
  97. container.register_transient<TEntrypoint>();
  98. route_table.register(route, typeof(TEntrypoint));
  99. }
  100. /** Registers a {@link StatumResource} for serving from `/_statum/resource/{name}`. */
  101. public void add_resource<T>() {
  102. container.register_startup<T>()
  103. .as<StatumResource>();
  104. }
  105. /**
  106. * Registers a {@link StatumAction}, auto-assigning it a GUID endpoint at
  107. * `/_statum/action/{guid}` (Spry-style — no hand-picked URI). Invoke the
  108. * action from the client by embedding an authored reference in state (see
  109. * {@link ActionRegistry.author}) and binding it with `stm-action`.
  110. */
  111. public void action<TAction>() {
  112. container.register_scoped<TAction>();
  113. action_registry.register<TAction>();
  114. }
  115. }
  116. }