using Invercargill; using Invercargill.DataStructures; using Invercargill.Mapping; namespace Statum { /** * Fluent builder for the directive list an entrypoint/action returns. * * Started via {@link StatumEntrypoint.directives}/{@link StatumAction.directives} * (bound to the handler's {@link StateService}). Each mutator returns the * builder, so directives chain: * * ``` * return directives() * .set(slot) // fresh slot: sign_slot -> set * .update(key, state) // mutate + auto-push to channels + sign -> set * .clear(other_key) * .notify("info", "Saved") * .navigate("/"); * ``` * * `set`/`update` are intents recorded in declaration order; pure directives * are stored inline. {@link build} is `async` and materialises the intents in * order (`update` yields to push to subscribed channels; `set` calls * `sign_slot`), preserving ordering, then returns the directives. */ public class DirectiveBuilder : Object { private StateService state_service; private Series items = new Series(); internal DirectiveBuilder(StateService state_service) { this.state_service = state_service; } /** Signs the (fresh) slot and emits a `set` directive for its frame. */ public DirectiveBuilder set(Slot slot) { items.add(new Item() { kind = ItemKind.SLOT, slot = slot }); return this; } /** Emits a `set` directive for an already-signed frame. */ public DirectiveBuilder set_frame(Model.FrameDto frame) { items.add(new Item() { kind = ItemKind.DIRECTIVE, directive = new Model.SetDirectiveDto.with_frame(frame) }); return this; } /** Mutates the cached slot (`update` = sign + auto-push to channels) and emits a `set`. */ public DirectiveBuilder update(string key, State state) { items.add(new Item() { kind = ItemKind.UPDATE, key = key, update_state = state }); return this; } /** Emits a `clear` directive. */ public DirectiveBuilder clear(string key) { items.add(new Item() { kind = ItemKind.DIRECTIVE, directive = new Model.ClearDirectiveDto.with_key(key) }); return this; } /** Emits an `invalidate` directive for a frame `signature` the server could not trust. */ public DirectiveBuilder invalidate(string signature) { items.add(new Item() { kind = ItemKind.DIRECTIVE, directive = new Model.InvalidateDirectiveDto.with_signature(signature) }); return this; } /** Emits a `subscribe` directive (realtime channel). */ public DirectiveBuilder subscribe(string key) { items.add(new Item() { kind = ItemKind.DIRECTIVE, directive = new Model.SubscribeDirectiveDto.with_key(key) }); return this; } /** Emits an `unsubscribe` directive. */ public DirectiveBuilder unsubscribe(string key) { items.add(new Item() { kind = ItemKind.DIRECTIVE, directive = new Model.UnsubscribeDirectiveDto.with_key(key) }); return this; } /** Emits a `transmit` directive. */ public DirectiveBuilder transmit(string key, bool retry = false) { items.add(new Item() { kind = ItemKind.DIRECTIVE, directive = new Model.TransmitDirectiveDto.with(key, retry) }); return this; } /** Emits a `notify` directive. */ public DirectiveBuilder notify(string kind, string message) { items.add(new Item() { kind = ItemKind.DIRECTIVE, directive = new Model.NotifyDirectiveDto.with(kind, message) }); return this; } /** Emits an `error` directive. */ public DirectiveBuilder error(string message, string? code = null, Properties? data = null) { items.add(new Item() { kind = ItemKind.DIRECTIVE, directive = new Model.ErrorDirectiveDto() { message = message, code = code, data = data }}); return this; } /** Emits a terminal `post` directive (full-page form POST). */ public DirectiveBuilder post(string uri, Properties data) { items.add(new Item() { kind = ItemKind.DIRECTIVE, directive = new Model.PostDirectiveDto.with(uri, data) }); return this; } /** Emits a terminal `navigate` directive. */ public DirectiveBuilder navigate(string uri) { items.add(new Item() { kind = ItemKind.DIRECTIVE, directive = new Model.NavigateDirectiveDto.with_uri(uri) }); return this; } /** Materialises intents and returns the directive lot, in declaration order. */ public async Lot build() throws GLib.Error { var directives = new Series(); foreach (var item in items) { switch (item.kind) { case ItemKind.DIRECTIVE: if (item.directive != null) { directives.add(item.directive); } break; case ItemKind.SLOT: if (item.slot != null) { var frame = state_service.sign_slot(((!)item.slot).id); if (frame != null) { directives.add(new Model.SetDirectiveDto.with_frame((!)frame)); } } break; case ItemKind.UPDATE: var frame = yield state_service.update(item.key, item.update_state); directives.add(new Model.SetDirectiveDto.with_frame(frame)); break; } } return directives; } /** Alias for {@link build}. */ public async Lot to_lot() throws GLib.Error { return yield build(); } private enum ItemKind { DIRECTIVE, SLOT, UPDATE } private class Item : Object { public ItemKind kind; public Model.DirectiveDto? directive; public Slot? slot; public State? update_state; public string key; } } }