ordinary_event_family.bend

bendvy-ecs-core@0.1.0.1 · 141 lines · 0 definitions

1import Base
2import owner_product.bend as Product
3import ordinary_event_declaration.bend as Event
4import event_runtime.bend as Ev
5import event_stream.bend as Leaf
6import event_publication.bend as Publication
7
8# Builder domains are operational declarations, not a second metadata list.
9# Detached left/right domain declarations for a heterogeneous event family.
10# Their nested shape matches the publication and storage product, not live readers.
11type Domains<A:Data,B:Data> is Data:Domains{first:A,second:B}
12# Typed family setup containing ordinary domains and closed storage/routing
13# operations. Runtime streams are constructed separately; this declaration
14# supplies no live cursor or affine payload. Raw constructors are trusted setup.
15# Family construction and reader lifecycle: docs/parity/typed-data-events.md.
16type Provision<S:Data,P:Data,D:Data,F:Type,-make:D -> Nat -> F,-sync:F -> Nat -> List<&2,P> -> F,-frame:F -> Nat -> F,-retain:F -> Nat -> List<&2,P> -> F & List<&2,P>,-inventory:F -> F & List<&2,String>> is Data:
17  Provision{domains:D}
18def leaf_make(~S:Data,~E:Data,domain:Event.Domain<S,E>,capacity:Nat) -> Leaf.Stream<S,E>:Leaf.provision(~S,~E,domain,capacity)
19def leaf_sync(~S:Data,~E:Data,stream:Leaf.Stream<S,E>,tick:Nat,values:List<&2,E>) -> Leaf.Stream<S,E>:Leaf.append(~S,~E,stream,tick,values)
20def leaf_frame(~S:Data,~E:Data,stream:Leaf.Stream<S,E>,tick:Nat) -> Leaf.Stream<S,E>:Leaf.frame(~S,~E,stream,tick)
21def leaf_retained(~S:Data,~E:Data,stream:Leaf.Stream<S,E>,remove:Bool,values:List<&2,E>) -> Leaf.Stream<S,E> & List<&2,E>:
22  match remove:
23    case False{}:(stream,values)
24    case True{}:(stream,[])
25def leaf_retain(~S:Data,~E:Data,stream:Leaf.Stream<S,E>,tick:Nat,values:List<&2,E>) -> Leaf.Stream<S,E> & List<&2,E>:
26  +retained=stream
27  leaf_retained(~S,~E,retained,Nat.is_le(tick,Leaf.dropped(~S,~E,retained)),values)
28def leaf_inventory(~S:Data,~E:Data,stream:Leaf.Stream<S,E>) -> Leaf.Stream<S,E> & List<&2,String>:
29  +retained=stream
30  (retained,[Leaf.inventory(~S,~E,retained)])
31# Declare one Data event family leaf from its schema-scoped domain. The
32# resulting provision supplies stream creation, synchronization and retention
33# operations, but does not create a stream until runtime setup invokes make.
34# Example: Family.leaf(~S,~E,Event.domain(~S,~E,"hit")).
35def leaf(~S:Data,~E:Data,domain:Event.Domain<S,E>) -> Provision<S,E,Event.Domain<S,E>,Leaf.Stream<S,E>,leaf_make(~S,~E),leaf_sync(~S,~E),leaf_frame(~S,~E),leaf_retain(~S,~E),leaf_inventory(~S,~E)>:Provision{domain}
36def pair_make(~DA:Data,~DB:Data,~FA:Type,~FB:Type,~makeA:DA -> Nat -> FA,~makeB:DB -> Nat -> FB,domains:Domains<DA,DB>,+capacity:Nat) -> Product.Owner<FA,FB>:
37  match domains:
38    case Domains{first,second}:Product.Owner{makeA(first,capacity),makeB(second,capacity)}
39def pair_sync(~A:Data,~B:Data,~FA:Type,~FB:Type,~syncA:FA -> Nat -> List<&2,A> -> FA,~syncB:FB -> Nat -> List<&2,B> -> FB,family:Product.Owner<FA,FB>,+tick:Nat,+records:List<&2,Publication.Publication<A,B>>) -> Product.Owner<FA,FB>:
40  match family:
41    case Product.Owner{first,second}:Product.Owner{syncA(first,tick,Publication.first_values(~A,~B,records)),syncB(second,tick,Publication.second_values(~A,~B,records))}
42def pair_frame(~FA:Type,~FB:Type,~frameA:FA -> Nat -> FA,~frameB:FB -> Nat -> FB,family:Product.Owner<FA,FB>,+tick:Nat) -> Product.Owner<FA,FB>:
43  match family:
44    case Product.Owner{first,second}:Product.Owner{frameA(first,tick),frameB(second,tick)}
45def pair_inventory_join(~FA:Type,~FB:Type,first:FA & List<&2,String>,second:FB & List<&2,String>) -> Product.Owner<FA,FB> & List<&2,String>:
46  (first,firstNames)=first
47  (second,secondNames)=second
48  (Product.Owner{first,second},Ev.append(~String,firstNames,secondNames))
49def pair_inventory(~FA:Type,~FB:Type,~inventoryA:FA -> FA & List<&2,String>,~inventoryB:FB -> FB & List<&2,String>,family:Product.Owner<FA,FB>) -> Product.Owner<FA,FB> & List<&2,String>:
50  match family:
51    case Product.Owner{first,second}:pair_inventory_join(~FA,~FB,inventoryA(first),inventoryB(second))
52def retained_tail(~A:Data,~B:Data,~F:Type,record:Publication.Publication<A,B>,returned:F & List<&2,Publication.Publication<A,B>>) -> F & List<&2,Publication.Publication<A,B>>:
53  match returned:
54    case (family,rest):(family,Publication.retained_join(~A,~B,record,rest))
55def retained_children(~A:Data,~B:Data,~FA:Type,~FB:Type,rest:Product.Owner<FA,FB> -> Product.Owner<FA,FB> & List<&2,Publication.Publication<A,B>>,first:FA & List<&2,A>,second:FB & List<&2,B>) -> Product.Owner<FA,FB> & List<&2,Publication.Publication<A,B>>:
56  (first,firstValues)=first
57  (second,secondValues)=second
58  retained_tail(~A,~B,~Product.Owner<FA,FB>,Publication.Publication{firstValues,secondValues},rest(Product.Owner{first,second}))
59def retained_record(~A:Data,~B:Data,~FA:Type,~FB:Type,~retainA:FA -> Nat -> List<&2,A> -> FA & List<&2,A>,~retainB:FB -> Nat -> List<&2,B> -> FB & List<&2,B>,rest:Product.Owner<FA,FB> -> Product.Owner<FA,FB> & List<&2,Publication.Publication<A,B>>,+tick:Nat,record:Publication.Publication<A,B>,family:Product.Owner<FA,FB>) -> Product.Owner<FA,FB> & List<&2,Publication.Publication<A,B>>:
60  match record family:
61    case Publication.Publication{a,b} Product.Owner{first,second}:retained_children(~A,~B,~FA,~FB,rest,retainA(first,tick,a),retainB(second,tick,b))
62def retained_one_children(~A:Data,~B:Data,~FA:Type,~FB:Type,first:FA & List<&2,A>,second:FB & List<&2,B>) -> Product.Owner<FA,FB> & Publication.Publication<A,B>:
63  (first,firstValues)=first
64  (second,secondValues)=second
65  (Product.Owner{first,second},Publication.Publication{firstValues,secondValues})
66def retained_one(~A:Data,~B:Data,~FA:Type,~FB:Type,~retainA:FA -> Nat -> List<&2,A> -> FA & List<&2,A>,~retainB:FB -> Nat -> List<&2,B> -> FB & List<&2,B>,+tick:Nat,record:Publication.Publication<A,B>,family:Product.Owner<FA,FB>) -> Product.Owner<FA,FB> & Publication.Publication<A,B>:
67  match record family:
68    case Publication.Publication{a,b} Product.Owner{first,second}:retained_one_children(~A,~B,~FA,~FB,retainA(first,tick,a),retainB(second,tick,b))
69def pair_retain_acc(~A:Data,~B:Data,~FA:Type,~FB:Type,~retainA:FA -> Nat -> List<&2,A> -> FA & List<&2,A>,~retainB:FB -> Nat -> List<&2,B> -> FB & List<&2,B>,records:List<&2,Publication.Publication<A,B>>,returned:Product.Owner<FA,FB> & Publication.Publication<A,B>,+tick:Nat,reversed:List<&2,Publication.Publication<A,B>>) -> Product.Owner<FA,FB> & List<&2,Publication.Publication<A,B>>:
70  match records:
71    case []:
72      (family,record)=returned
73      (family,List.reverse(&2,Publication.Publication<A,B>,Publication.retained_join(~A,~B,record,reversed)))
74    case head <> rest:
75      (family,record)=returned
76      pair_retain_acc(~A,~B,~FA,~FB,~retainA,~retainB,rest,retained_one(~A,~B,~FA,~FB,~retainA,~retainB,tick,head,family),tick,Publication.retained_join(~A,~B,record,reversed))
77def pair_retain_loop(~A:Data,~B:Data,~FA:Type,~FB:Type,~retainA:FA -> Nat -> List<&2,A> -> FA & List<&2,A>,~retainB:FB -> Nat -> List<&2,B> -> FB & List<&2,B>,records:List<&2,Publication.Publication<A,B>>,family:Product.Owner<FA,FB>,+tick:Nat) -> Product.Owner<FA,FB> & List<&2,Publication.Publication<A,B>>:
78  match records:
79    case []:(family,[])
80    case record <> rest:pair_retain_acc(~A,~B,~FA,~FB,~retainA,~retainB,rest,retained_one(~A,~B,~FA,~FB,~retainA,~retainB,tick,record,family),tick,[])
81def pair_retain(~A:Data,~B:Data,~FA:Type,~FB:Type,~retainA:FA -> Nat -> List<&2,A> -> FA & List<&2,A>,~retainB:FB -> Nat -> List<&2,B> -> FB & List<&2,B>,family:Product.Owner<FA,FB>,tick:Nat,records:List<&2,Publication.Publication<A,B>>) -> Product.Owner<FA,FB> & List<&2,Publication.Publication<A,B>>:
82  pair_retain_loop(~A,~B,~FA,~FB,~retainA,~retainB,records,family,tick)
83
84def node_make(~DA:Data,~DB:Data,~FA:Type,~FB:Type,~makeA:DA -> Nat -> FA,~makeB:DB -> Nat -> FB,domains:Domains<DA,DB>,capacity:Nat) -> Product.Owner<FA,FB>:pair_make(~DA,~DB,~FA,~FB,~makeA,~makeB,domains,capacity)
85def node_sync(~A:Data,~B:Data,~FA:Type,~FB:Type,~syncA:FA -> Nat -> List<&2,A> -> FA,~syncB:FB -> Nat -> List<&2,B> -> FB,family:Product.Owner<FA,FB>,tick:Nat,records:List<&2,Publication.Publication<A,B>>) -> Product.Owner<FA,FB>:pair_sync(~A,~B,~FA,~FB,~syncA,~syncB,family,tick,records)
86def node_frame(~FA:Type,~FB:Type,~frameA:FA -> Nat -> FA,~frameB:FB -> Nat -> FB,family:Product.Owner<FA,FB>,tick:Nat) -> Product.Owner<FA,FB>:pair_frame(~FA,~FB,~frameA,~frameB,family,tick)
87# Compose two event provisions under the same schema in left/right order.
88# Publication wrappers and family storage become matching nested products;
89# each leaf retains its own typed stream. Composition neither publishes records
90# nor combines independent reader cursors into one. Nest branches for more types.
91def branch(~S:Data,~A:Data,~B:Data,~DA:Data,~DB:Data,~FA:Type,~FB:Type,~makeA:DA -> Nat -> FA,~makeB:DB -> Nat -> FB,~syncA:FA -> Nat -> List<&2,A> -> FA,~syncB:FB -> Nat -> List<&2,B> -> FB,~frameA:FA -> Nat -> FA,~frameB:FB -> Nat -> FB,~retainA:FA -> Nat -> List<&2,A> -> FA & List<&2,A>,~retainB:FB -> Nat -> List<&2,B> -> FB & List<&2,B>,~inventoryA:FA -> FA & List<&2,String>,~inventoryB:FB -> FB & List<&2,String>,first:Provision<S,A,DA,FA,makeA,syncA,frameA,retainA,inventoryA>,second:Provision<S,B,DB,FB,makeB,syncB,frameB,retainB,inventoryB>) -> Provision<S,Publication.Publication<A,B>,Domains<DA,DB>,Product.Owner<FA,FB>,node_make(~DA,~DB,~FA,~FB,~makeA,~makeB),node_sync(~A,~B,~FA,~FB,~syncA,~syncB),node_frame(~FA,~FB,~frameA,~frameB),pair_retain(~A,~B,~FA,~FB,~retainA,~retainB),pair_inventory(~FA,~FB,~inventoryA,~inventoryB)>:
92  match first second:
93    case Provision{first} Provision{second}:Provision{Domains{first,second}}
94
95# Typed path to one event stream in a family, with owner-threading take/put
96# operations. This Data path owns no stream or reader; raw indices are trusted
97# setup. Equal payload types do not justify substituting unrelated family paths.
98type Selection<S:Data,E:Data,F:Type,-take:F -> F & Leaf.Stream<S,E>,-put:F -> Leaf.Stream<S,E> -> F> is Data:Selection{}
99def selected_leaf(~S:Data,~E:Data,stream:Leaf.Stream<S,E>) -> Leaf.Stream<S,E> & Leaf.Stream<S,E>:
100  +retained=stream
101  (retained,retained)
102def replaced_leaf(~S:Data,~E:Data,_:Leaf.Stream<S,E>,stream:Leaf.Stream<S,E>) -> Leaf.Stream<S,E>:stream
103# Select a stream that is itself the complete family owner. The selection
104# records closed extraction/replacement operations and performs neither here.
105def select_leaf(~S:Data,~E:Data) -> Selection<S,E,Leaf.Stream<S,E>,selected_leaf(~S,~E),replaced_leaf(~S,~E)>:Selection{}
106def selected_left(~S:Data,~E:Data,~FA:Type,~FB:Type,second:FB,returned:FA & Leaf.Stream<S,E>) -> Product.Owner<FA,FB> & Leaf.Stream<S,E>:
107  match returned:
108    case (first,stream):(Product.Owner{first,second},stream)
109def left_take(~S:Data,~E:Data,~FA:Type,~FB:Type,~take:FA -> FA & Leaf.Stream<S,E>,family:Product.Owner<FA,FB>) -> Product.Owner<FA,FB> & Leaf.Stream<S,E>:
110  match family:
111    case Product.Owner{first,second}:selected_left(~S,~E,~FA,~FB,second,take(first))
112def left_put(~S:Data,~E:Data,~FA:Type,~FB:Type,~put:FA -> Leaf.Stream<S,E> -> FA,family:Product.Owner<FA,FB>,stream:Leaf.Stream<S,E>) -> Product.Owner<FA,FB>:
113  match family:
114    case Product.Owner{first,second}:Product.Owner{put(first,stream),second}
115def selected_right(~S:Data,~E:Data,~FA:Type,~FB:Type,first:FA,returned:FB & Leaf.Stream<S,E>) -> Product.Owner<FA,FB> & Leaf.Stream<S,E>:
116  match returned:
117    case (second,stream):(Product.Owner{first,second},stream)
118def right_take(~S:Data,~E:Data,~FA:Type,~FB:Type,~take:FB -> FB & Leaf.Stream<S,E>,family:Product.Owner<FA,FB>) -> Product.Owner<FA,FB> & Leaf.Stream<S,E>:
119  match family:
120    case Product.Owner{first,second}:selected_right(~S,~E,~FA,~FB,first,take(second))
121def right_put(~S:Data,~E:Data,~FA:Type,~FB:Type,~put:FB -> Leaf.Stream<S,E> -> FB,family:Product.Owner<FA,FB>,stream:Leaf.Stream<S,E>) -> Product.Owner<FA,FB>:
122  match family:
123    case Product.Owner{first,second}:Product.Owner{first,put(second,stream)}
124# Lift a leaf selection through the left side of a family product.
125# The generated path preserves the right sibling when accessing/replacing
126# the selected stream; it does not allocate or advance a reader.
127def select_left(~S:Data,~E:Data,~FA:Type,~FB:Type,~take:FA -> FA & Leaf.Stream<S,E>,~put:FA -> Leaf.Stream<S,E> -> FA,_:Selection<S,E,FA,take,put>) -> Selection<S,E,Product.Owner<FA,FB>,left_take(~S,~E,~FA,~FB,~take),left_put(~S,~E,~FA,~FB,~put)>:Selection{}
128# Lift a leaf selection through the right side of a family product,
129# preserving the left sibling. Compose paths to address nested families.
130def select_right(~S:Data,~E:Data,~FA:Type,~FB:Type,~take:FB -> FB & Leaf.Stream<S,E>,~put:FB -> Leaf.Stream<S,E> -> FB,_:Selection<S,E,FB,take,put>) -> Selection<S,E,Product.Owner<FA,FB>,right_take(~S,~E,~FA,~FB,~take),right_put(~S,~E,~FA,~FB,~put)>:Selection{}
131# Identity injection for a Data event in a leaf publication. Does not send
132# it or establish a publication tick; runtime publication does that separately.
133def inject_leaf(~E:Data,event:E) -> E:event
134# Wrap one event on the left side of a heterogeneous publication with an
135# empty right list. The closed inject callback embeds nested leaf shapes;
136# this creates a Data publication value, without mutating any runtime stream.
137def inject_left(~E:Data,~A:Data,~B:Data,~inject:E -> A,event:E) -> Publication.Publication<A,B>:Publication.Publication{[inject(event)],[]}
138# Wrap one event on the right side of a heterogeneous publication with an
139# empty left list. Retains the nested injection shape; does not publish or
140# change a cursor until the runtime processes the resulting value.
141def inject_right(~E:Data,~A:Data,~B:Data,~inject:E -> B,event:E) -> Publication.Publication<A,B>:Publication.Publication{[],[inject(event)]}