Open Kelerchian opened 1 year ago
zod
.typescript
type.zod
or io-ts
may be used to ease the writing of JSON schema.[swarm_identifier]
uses hash, produced by hashing the JSON schema
. { type: "string" }
and `{ allOf: [{ type: "string" }] } should produce the same hash{ "type": "object", "properties": { "a": { "type": "number" }, "b": { "type": "string" } } }
and { "type": "object", "properties": { "b": { "type": "string" }, "a": { "type": "number" } } }
should produce the same hashYes, we’re thinking along similar lines here. I’m not yet sure how to best integrate the detailed state computations — without them (and consequently without event payload data) we could condense everything down to the types given in the ECOOP paper. But that’s not expressive enough for real protocols.
One idea here is that we generate the code for the machine definition but with placeholders for the command hooks and event transitions. Forgetting to overwrite one of the placeholders would immediately lead to an exception. This way we can keep the generated code in a separate file — messing with an existing user file is always tricky.
that’s not expressive enough for real protocols.
Not quite sure I understand.
One idea here is that we generate the code for the machine definition but with placeholders for the command hooks and event transitions. Forgetting to overwrite one of the placeholders would immediately lead to an exception.
Thinking the same too.
Anyway, for the current state of machine-runner, with TypeScript and stuff, we can add manual versioning API so that tags that are generated are "[swarmprotocolname]", "[swarmprotocolname]:[version]", and then withId
will result in "[swarmprotocolname]:[version]:[id]".
@rkuhn this came up during examining SW's unit test problem: a new API that can be compatible with our current API
const protocol =
Protocol
.build("theprotocolname", Events.all)
.roles([
"Manager",
"Storage"
])
.states([
"StateA",
"StateB",
"Statec",
"StateD",
"StateE",
"StateF",
"StateG",
"StateH",
])
.initial((states) => states.StateA)
// ^^^^^^^^^^^^^
// hinted
.transitions(({
command, states, roles
}) => {
/**
* List transitions here, the transitions are best written chronologically from the top to bottom
*/
command(roles.Manager, states.StateA, "commandName", [Events.B], states.stateB)
// ^^^^^^^^^^^^^ ^^^^^^^^^^^ ^^^^^^^^^^^^^
// hinted constrained hinted
command(roles.Storage, states.StateB, "commandName", [Events.C], states.stateC)
command(roles.Manager, states.StateB, "commandName", [Events.D], states.stateD)
command(roles.Storage, states.StateD, "commandName", [Events.E], states.stateE)
command(roles.Manager, states.StateC, "commandName", [Events.F], states.stateF)
})
/* alternatively, a command can instead take this shape if we want to process the type information
but the above one is safer */
.command(({states, roles}) => [roles.Manager, states.statesA, "commandName", [Events.B], states.stateB])
// ^^^^^^^^^^^^^ ^^^^^^^^^^^^^ ^^^^^^^^^^^ ^^^^^^^^^^^^^
// hinted hinted constrained hinted
.command(({states, roles}) => [roles.Storage, states.StateB, "commandName", [Events.C], states.stateC])
.finish()
/**
* used in machine-check, produce SwarmProtocolType
*/
const protocolAnalysis = protocol.createJSONForAnalysis();
// Role creation
// ===============
const ForManager = protocol.roles.Manager.createProtocol()
// State Creation
// ===============
const StateAForManager =
ForManager
.states.stateA.design()
// ^^^^^^
// hinted
.withPayload<ThePayload>()
/* probably is not safe from TS version change */
.commands.commandName.define([Events.B], (ctx, param => [param]))
/* or alternatively command can take place like this */
.command(protocol.commands.commandName, [Events.B], (ctx, param) => [param])
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
// hinted if type is previously registered
.finish()
// Simpler checks
// NOTE: Throws if somehow this method is called after `checkSwarmProtocol` below is called for this state
ForManager.react(...)
// MACHINE CHECK
// ===============
// Simpler checks
const allRoles = [ForManager, ForStorage] as const
// NOTE:
// - machine-check knows machine-runner has `createJSONForAnalysis` method. It'll use it to grab the subscriptions.
// - Initial is provided
checkSwarmProtocol(protocol, allRoles)
checkProjection(protocol, allRoles, ForManager)
A completely different approach could be to split state payload computation from state transitions: the observer sees the state name and the sequence of events that led to this state (i.e. the unhandled ones are filtered out). Payload computation could then be fully decoupled and independently versioned.
Another (orthogonal) choice would be to use hashing instead of manual versioning, identifying a swarm protocol with the hash of its state machine description. This implies that new instances will not process events written by old instances, which should be fine for many use-cases. Where continuing an old process with new logic is required, a translation scheme like Cambria would be needed, explicitly opting into the processing of old events via a transformation function.
the hash of its state machine description
But this will require the state machine description to be fully written in value, not type (except if we want to include manually using typescript API in the compilation process).
Although, in my opinion, the semantics of event sets and event chains are the ones truly needing versioning, while state payload does not.
But this will require the state machine description to be fully written in value, not type (except if we want to include manually using typescript API in the compilation process).
Right, using hashing without splitting the state machine from the payload computation makes this more difficult. Anyway, these are future thoughts, I want to first await real world feedback on our current APIs before starting this.
Potential problem
This issue will list some potential problems the current API does not handle.
UB from incorrect event payload schema in storage
Specific-role projection is not well-formed.
Proposed Solution
The first problem is solvable by caveats, versioning how-tos and best practices, and examples.
The second problem is negligible depending on how the developer can afford to sacrifice writability for the sake of easily solving the distributed-state-machine problem.
However, here is an alternative API:
The 3 steps compilation