Phase 1 Implementation Plan #4
Closed
SeanTAllen
started this conversation in
Research
Replies: 1 comment
-
|
Implemented in #5. |
Beta Was this translation helpful? Give feedback.
0 replies
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
Implementation plan for Phase 1 of livery, derived from the design in #3.
Scope
Server-side Pony library only. The JS client (deliverable 7) lives in a separate repo per the design's scope section. Covers deliverables 1-6, 8-9.
Dependencies
usegithub.com/ponylang/mare.gitrelease-0.1.1"mare"github.com/ponylang/templates.git0.3.0"templates"github.com/ponylang/json-ng.git0.3.0"json"Files
Remove
livery/main.pony— placeholder Main actor, incompatible with library packageexamples/basic/main.pony— placeholder, replaced by counter exampleCreate: Core Library (
livery/)livery.pony— Package docstring guiding users toward theLiveViewtrait andSocketAPI. Notes thatHtmlTemplate(from templates library) is recommended for rendering.live_view.pony—LiveViewtrait (Phase 1, nohandle_info):mountinitializes assigns on the socket.handle_eventresponds to client interactions by updating assigns.renderproduces HTML from the current assigns. Partial — the framework handles errors gracefully (keeps the last successfully rendered HTML).boxreceiver because rendering should be a pure function of data.assigns.pony—Assignsclass backed byTemplateValues reffrom the templates library:fun ref update(key: String, value: (String | TemplateValue))— delegates to TemplateValues, sets dirty flagfun box apply(key: String): TemplateValue ?— delegates to TemplateValuesfun box changed(): Bool— returns dirty flagfun ref clear_changes()— resets dirty flagfun box template_values(): TemplateValues box— returns backing store for use withHtmlTemplate.render()Values are
(String | TemplateValue)— the same types the templates library accepts. The backingTemplateValueshandles wrapping plain strings automatically.socket.pony—Socketclass:Assigns refandArray[(String val, JsonValue)] ref— shared references owned by_Connectionfun ref assign(key: String, value: (String | TemplateValue))— delegates to Assigns.updatefun box get_assign(key: String): TemplateValue ?— delegates to Assigns.applyfun ref push_event(event: String val, payload: JsonValue)— appends to the shared pending events array, flushed by_Connectionafter renderfactory.pony—Factoryinterface:Structural typing allows lambdas as factories:
{(): LiveView ref^ ? => CounterView?}.router.pony—Routerref builder +Routesval (immutable, shareable):Router.route(path: String, factory: Factory)— appends a routeRouter.build(): Routes val— freezes the route table by creating an iso Array and consuming it intoRoutesRouteshas a package-private constructor (only callable fromRouter.build())Routes.apply(path: String): (Factory | None)— strips query string internally before matching, then does a linear scan lookup. O(n) is fine for typical route counts. Callers don't need to remember to strip query strings.listener.pony—Listeneractor:TCPListenerActor(from lori, via mare)new create(auth: TCPListenAuth, host: String, port: String, routes: Routes val, out: OutStream)WebSocketConfig(host, port)internally with sensible defaults for max message/handshake sizeTCPServerAuthfor passing to connection actors_on_accept(fd)creates_Connectionwith auth, fd, config, and routes_on_listen_failure()prints a message tooutincluding the host and port so users know why the server didn't start_connection.pony—_Connectionactor:WebSocketServerActor(from mare)_assigns: Assigns ref,_pending_events: Array[(String val, JsonValue)],_socket: Socket ref(wraps shared refs to assigns and pending events)_ws: WebSocketServer,_view: (LiveView ref | None),_last_html: String val,_router: Routes val_view = Noneinitially. Mare's_on_acceptfires before the HTTP upgrade handshake completes, so the URI isn't known yet.Lifecycle:
on_open(request): Look up factory from router usingrequest.uri(query string stripping is handled byRoutes.apply), call factory in try block (sends error + closes on failure), callmount(_socket), render, send HTML, arm 60s idle timeout via lorion_text_message(data): Decode JSON via_WireProtocol.decode_client_message(data), dispatch tohandle_eventor heartbeat ackhandle_event: If_assigns.changed(), re-render (on failure: keep_last_html, send error to client), clear changes, flush pending push eventson_idle_timeout: Close WebSocketon_closed: No-op in Phase 1 (no PubSub subscriptions to clean up)_wire_protocol.pony— Wire protocol encode/decode:_WireProtocolprimitive with static methods:encode_render(html: String val): String val—{"t":"render","html":"..."}encode_heartbeat_ack(): String val—{"t":"heartbeat_ack"}encode_push_event(event: String val, payload: JsonValue): String val—{"t":"push","e":"...","p":...}encode_error(reason: String val): String val—{"t":"error","reason":"..."}decode_client_message(data: String val): (_ClientMessage | _WireError)— parses JSON, returns structured message or error_ClientMessagetype alias:(_EventMessage | _HeartbeatMessage)_EventMessageclass:let event: String val,let payload: JsonValue_HeartbeatMessageprimitive (no fields — our protocol has no ref tracking)_WireErrorclass:let reason: String val_unreachable.pony—_Unreachableprimitive for impossible else branches.Create: Example
examples/counter/main.pony— Counter app from the design doc:examples/README.md— Brief description of the counter example.Modify
corral.json— Add mare, templates, and json-ng as dependencies.Shared-State Pattern
_ConnectionownsAssigns refandArray[(String val, JsonValue)] ref. It passes refs to both intoSocket's public constructor. When user code callssocket.assign(...), it mutates the sharedAssigns. After the handler returns,_Connectionchecks_assigns.changed()directly. All refs stay within the same actor's isolation boundary — no capability issues.Tests (
livery/_test.pony)Single test runner per project convention. All test types have
\nodoc\annotation.Assigns Property Tests (PonyCheck)
updatecalls,changed()istrue. Afterclear_changes(),changed()isfalse. One moreupdatemakeschanged()trueagain.update(key, value)thenapply(key)?.string()?returns the original string.template_values()(key)?.string()?matches what was assigned viaupdate.Wire Protocol Tests
encode_renderproduces valid JSON with correct"t"and"html"fields. Same for other encode functions._EventMessagefields match._HeartbeatMessage."t"field, unknown message type → verify_WireErrorwith descriptive reason.Router Tests
None.Nonefor any path."/path?foo=bar"matches a route registered as"/path".Socket Tests
push_eventappends to the shared pending events array (verify array contents after call).Not Tested in Phase 1
Connection actor integration tests require WebSocket client infrastructure (mare is server-only). These will be added when the JS client is available for end-to-end testing.
Build and Test
What's Not in Phase 1
handle_info/ PubSub (Phase 2)lv-change/lv-submit(Phase 3)Beta Was this translation helpful? Give feedback.
All reactions