ordinary_query_declaration.bend
bendvy-ecs-core@0.1.0.1 · 184 lines · 0 definitions
1import Base
2import bendvy-ecs-core@0.1.0.1/column.bend as Col
3import bendvy-ecs-core@0.1.0.1/component.bend as Cmp
4import bendvy-ecs-core@0.1.0.1/persistence_declaration.bend as Recipe
5import bendvy-ecs-core@0.1.0.1/ordinary_declaration.bend as Identity
6import bendvy-ecs-core@0.1.0.1/declared_family.bend as Family
7import bendvy-ecs-core@0.1.0.1/snapshot_leaf.bend as Leaf
8import bendvy-ecs-core@0.1.0.1/decode_data.bend as D
9import bendvy-ecs-core@0.1.0.1/compose.bend as Q
10import bendvy-ecs-core@0.1.0.1/capabilities.bend as Cap
11import bendvy-ecs-core@0.1.0.1/world.bend as W
12
13# One constructor owns both operational grant/matcher and detached syntax.
14# Metadata is library-produced; the application supplies no metadata argument.
15# Query clause kind used by declaration-derived inspection and access metadata.
16type Mode is Data:
17 Read{}
18 Write{}
19 Optional{}
20 With{}
21 Without{}
22 Added{}
23 Changed{}
24# Detached schema-scoped description of one component query requirement.
25type Clause<S:Data> is Data:
26 Clause{name:String,mode:Mode}
27# Typed query declaration: grants, row selection and debug clauses share one source.
28# Build with read/write/filter constructors and pair; raw construction is a trusted
29# adapter boundary. H is threaded affinely; G determines available operations.
30type Declaration<-S:Data,H:Type,G:Type> is Type:
31 Declaration{grant:G,select:H -> H & Bool,clauses:List<&2,Clause<S>>}
32# Return the declaration owner together with its detached debug clauses.
33# Does not inspect a world, execute a query or advance a system cursor.
34def observe(~S:Data,~H:Type,~G:Type,declaration:Declaration<S,H,G>) -> Declaration<S,H,G> & List<&2,Clause<S>>:
35 match declaration:
36 case Declaration{grant,select,+clauses}:(Declaration{grant,select,clauses},clauses)
37# Consume the declaration and extract its typed operations for a system body.
38# This supplies only G; a read declaration cannot confer write operations.
39def grant(~S:Data,~H:Type,~G:Type,declaration:Declaration<S,H,G>) -> G:
40 match declaration:
41 case Declaration{grant,_,_}:grant
42
43# Constructed-component counterpart of read, using the declaration-bound save recipe.
44# take/put must preserve the matching Leaf.Component and all remaining storage.
45# Requires presence and returns a detached V while retaining the affine payload.
46# Reads return Found, ComponentAbsent or MissingEntity (including stale/foreign).
47def constructed_read(~S:Data,~P:Type,~Store:Type,~R:Type,~E:Data,~Rest:Type,~V:Data,~Token:Data,~identity:Identity.Declaration<S,P>,~save:P -> P & D.Raw,~view:P -> P & V,~take:Store -> Rest & Leaf.Component<S,P,identity,save>,~put:Rest -> Leaf.Component<S,P,identity,save> -> Store) -> Declaration<S,Q.Frame<S,Store,R,E>,Cap.Read<Q.Frame<S,Store,R,E>,V>>:
48 Declaration{Q.read_family(~S,~Store,~R,~E,~(Rest & Recipe.Constructed<S,P,identity,save>),~P,~V,~Token,~Family.take_column(~S,~P,~Store,~Rest,~identity,~save,~take),~Family.put_column(~S,~P,~Store,~Rest,~identity,~save,~put),~view,~Family.family(~S,~P,~Store,~Rest,~V,~Token,~identity,~save,~view,~take,~put)),Q.family_match(~S,~Store,~R,~E,~(Rest & Recipe.Constructed<S,P,identity,save>),~P,~V,~Token,~Family.take_column(~S,~P,~Store,~Rest,~identity,~save,~take),~Family.put_column(~S,~P,~Store,~Rest,~identity,~save,~put),~view,~Family.family(~S,~P,~Store,~Rest,~V,~Token,~identity,~save,~view,~take,~put),~Q.Required{}),[Clause{Identity.key(~S,~P,identity),Read{}}]}
49
50# Constructed-component counterpart of write, using the declaration-bound save recipe.
51# take/put must preserve the matching Leaf.Component and all remaining storage.
52# Requires presence and grants transactional payload replacement, including rollback.
53# Failed sets retain an error in Frame; Compose reports AccessError and rolls back.
54# The set grant consumes P and does not return rejected input to the caller.
55def constructed_write(~S:Data,~P:Type,~Store:Type,~R:Type,~E:Data,~Rest:Type,~V:Data,~Token:Data,~identity:Identity.Declaration<S,P>,~save:P -> P & D.Raw,~view:P -> P & V,~take:Store -> Rest & Leaf.Component<S,P,identity,save>,~put:Rest -> Leaf.Component<S,P,identity,save> -> Store) -> Declaration<S,Q.Frame<S,Store,R,E>,Cap.Write<Q.Frame<S,Store,R,E>,P,V>>:
56 Declaration{Q.write_family(~S,~Store,~R,~E,~(Rest & Recipe.Constructed<S,P,identity,save>),~P,~V,~Token,~Family.take_column(~S,~P,~Store,~Rest,~identity,~save,~take),~Family.put_column(~S,~P,~Store,~Rest,~identity,~save,~put),~view,~Family.family(~S,~P,~Store,~Rest,~V,~Token,~identity,~save,~view,~take,~put)),Q.family_match(~S,~Store,~R,~E,~(Rest & Recipe.Constructed<S,P,identity,save>),~P,~V,~Token,~Family.take_column(~S,~P,~Store,~Rest,~identity,~save,~take),~Family.put_column(~S,~P,~Store,~Rest,~identity,~save,~put),~view,~Family.family(~S,~P,~Store,~Rest,~V,~Token,~identity,~save,~view,~take,~put),~Q.Required{}),[Clause{Identity.key(~S,~P,identity),Write{}}]}
57
58# Constructed-component counterpart of optional, using the declaration-bound save recipe.
59# take/put must preserve the matching Leaf.Component and all remaining storage.
60# Allows absence; reads return Found, ComponentAbsent or MissingEntity.
61# Stale/foreign handles yield MissingEntity, never an optional matching row.
62def constructed_optional(~S:Data,~P:Type,~Store:Type,~R:Type,~E:Data,~Rest:Type,~V:Data,~Token:Data,~identity:Identity.Declaration<S,P>,~save:P -> P & D.Raw,~view:P -> P & V,~take:Store -> Rest & Leaf.Component<S,P,identity,save>,~put:Rest -> Leaf.Component<S,P,identity,save> -> Store) -> Declaration<S,Q.Frame<S,Store,R,E>,Cap.Read<Q.Frame<S,Store,R,E>,V>>:
63 Declaration{Q.read_family(~S,~Store,~R,~E,~(Rest & Recipe.Constructed<S,P,identity,save>),~P,~V,~Token,~Family.take_column(~S,~P,~Store,~Rest,~identity,~save,~take),~Family.put_column(~S,~P,~Store,~Rest,~identity,~save,~put),~view,~Family.family(~S,~P,~Store,~Rest,~V,~Token,~identity,~save,~view,~take,~put)),Q.family_match(~S,~Store,~R,~E,~(Rest & Recipe.Constructed<S,P,identity,save>),~P,~V,~Token,~Family.take_column(~S,~P,~Store,~Rest,~identity,~save,~take),~Family.put_column(~S,~P,~Store,~Rest,~identity,~save,~put),~view,~Family.family(~S,~P,~Store,~Rest,~V,~Token,~identity,~save,~view,~take,~put),~Q.Optional{}),[Clause{Identity.key(~S,~P,identity),Optional{}}]}
64
65# Constructed-component counterpart of with_component, using the declaration-bound save recipe.
66# take/put must preserve the matching Leaf.Component and all remaining storage.
67# Requires presence without granting payload access.
68def constructed_with_component(~S:Data,~P:Type,~Store:Type,~R:Type,~E:Data,~Rest:Type,~V:Data,~Token:Data,~identity:Identity.Declaration<S,P>,~save:P -> P & D.Raw,~view:P -> P & V,~take:Store -> Rest & Leaf.Component<S,P,identity,save>,~put:Rest -> Leaf.Component<S,P,identity,save> -> Store) -> Declaration<S,Q.Frame<S,Store,R,E>,Unit>:
69 Declaration{Unit{},Q.family_match(~S,~Store,~R,~E,~(Rest & Recipe.Constructed<S,P,identity,save>),~P,~V,~Token,~Family.take_column(~S,~P,~Store,~Rest,~identity,~save,~take),~Family.put_column(~S,~P,~Store,~Rest,~identity,~save,~put),~view,~Family.family(~S,~P,~Store,~Rest,~V,~Token,~identity,~save,~view,~take,~put),~Q.Required{}),[Clause{Identity.key(~S,~P,identity),With{}}]}
70
71# Constructed-component counterpart of without_component, using the declaration-bound save recipe.
72# take/put must preserve the matching Leaf.Component and all remaining storage.
73# Requires absence without granting payload access.
74def constructed_without_component(~S:Data,~P:Type,~Store:Type,~R:Type,~E:Data,~Rest:Type,~V:Data,~Token:Data,~identity:Identity.Declaration<S,P>,~save:P -> P & D.Raw,~view:P -> P & V,~take:Store -> Rest & Leaf.Component<S,P,identity,save>,~put:Rest -> Leaf.Component<S,P,identity,save> -> Store) -> Declaration<S,Q.Frame<S,Store,R,E>,Unit>:
75 Declaration{Unit{},Q.family_match(~S,~Store,~R,~E,~(Rest & Recipe.Constructed<S,P,identity,save>),~P,~V,~Token,~Family.take_column(~S,~P,~Store,~Rest,~identity,~save,~take),~Family.put_column(~S,~P,~Store,~Rest,~identity,~save,~put),~view,~Family.family(~S,~P,~Store,~Rest,~V,~Token,~identity,~save,~view,~take,~put),~Q.Absent{}),[Clause{Identity.key(~S,~P,identity),Without{}}]}
76
77# Constructed-component counterpart of added, using the declaration-bound save recipe.
78# take/put must preserve the matching Leaf.Component and all remaining storage.
79# Matches added stamps relative to the executing system cursor; grants no access.
80def constructed_added(~S:Data,~P:Type,~Store:Type,~R:Type,~E:Data,~Rest:Type,~V:Data,~Token:Data,~identity:Identity.Declaration<S,P>,~save:P -> P & D.Raw,~view:P -> P & V,~take:Store -> Rest & Leaf.Component<S,P,identity,save>,~put:Rest -> Leaf.Component<S,P,identity,save> -> Store) -> Declaration<S,Q.Frame<S,Store,R,E>,Unit>:
81 Declaration{Unit{},Q.lifecycle_match(~S,~Store,~R,~E,~(Rest & Recipe.Constructed<S,P,identity,save>),~P,~V,~Token,~Family.take_column(~S,~P,~Store,~Rest,~identity,~save,~take),~Family.put_column(~S,~P,~Store,~Rest,~identity,~save,~put),~view,~Family.family(~S,~P,~Store,~Rest,~V,~Token,~identity,~save,~view,~take,~put),~Q.Added{}),[Clause{Identity.key(~S,~P,identity),Added{}}]}
82
83# Constructed-component counterpart of changed, using the declaration-bound save recipe.
84# take/put must preserve the matching Leaf.Component and all remaining storage.
85# Matches changed stamps relative to the executing system cursor; grants no access.
86def constructed_changed(~S:Data,~P:Type,~Store:Type,~R:Type,~E:Data,~Rest:Type,~V:Data,~Token:Data,~identity:Identity.Declaration<S,P>,~save:P -> P & D.Raw,~view:P -> P & V,~take:Store -> Rest & Leaf.Component<S,P,identity,save>,~put:Rest -> Leaf.Component<S,P,identity,save> -> Store) -> Declaration<S,Q.Frame<S,Store,R,E>,Unit>:
87 Declaration{Unit{},Q.lifecycle_match(~S,~Store,~R,~E,~(Rest & Recipe.Constructed<S,P,identity,save>),~P,~V,~Token,~Family.take_column(~S,~P,~Store,~Rest,~identity,~save,~take),~Family.put_column(~S,~P,~Store,~Rest,~identity,~save,~put),~view,~Family.family(~S,~P,~Store,~Rest,~V,~Token,~identity,~save,~view,~take,~put),~Q.Changed{}),[Clause{Identity.key(~S,~P,identity),Changed{}}]}
88
89# Read the selected component through a read declaration, returning owner H
90# with Access<V>. The detached Data view does not copy the affine payload.
91# Results are Found, ComponentAbsent or MissingEntity; stale/foreign handles
92# produce MissingEntity. No payload owner is transferred to the reader.
93def execute_read(~S:Data,~H:Type,~V:Data,~declaration:Declaration<S,H,Cap.Read<H,V>>,owner:H) -> H & Cmp.Access<V>:
94 Cap.read(~H,~V,~grant(~S,~H,~Cap.Read<H,V>,declaration),owner)
95
96# Consume the declaration to obtain its owner-threading row predicate.
97# Selection grants no additional component access and does not enumerate rows.
98def selector(~S:Data,~H:Type,~G:Type,declaration:Declaration<S,H,G>) -> H -> H & Bool:
99 match declaration:
100 case Declaration{_,select,_}:select
101# Consume the declaration and return detached schema-scoped query metadata.
102# Prefer observe when the caller also needs to retain the declaration.
103def clauses(~S:Data,~H:Type,~G:Type,declaration:Declaration<S,H,G>) -> List<&2,Clause<S>>:
104 match declaration:
105 case Declaration{_,_,clauses}:clauses
106def selected_right(~H:Type,selected:Bool,observed:H & Bool) -> H & Bool:
107 match observed:
108 case (owner,right):(owner,Bool.and(selected,right))
109def selected_left(~H:Type,~right:H -> H & Bool,observed:H & Bool) -> H & Bool:
110 match observed:
111 case (owner,False{}):(owner,False{})
112 case (owner,True{}):right(owner)
113def both(~H:Type,~left:H -> H & Bool,~right:H -> H & Bool,owner:H) -> H & Bool:
114 selected_left(~H,~right,left(owner))
115def pair_selected(~H:Type,right:H -> H & Bool,observed:H & Bool) -> H & Bool:
116 match observed:
117 case (owner,False{}):(owner,False{})
118 case (owner,True{}):right(owner)
119def pair_select(~H:Type,left:H -> H & Bool,right:H -> H & Bool,owner:H) -> H & Bool:
120 pair_selected(~H,right,left(owner))
121def pair_parts(~S:Data,~H:Type,~L:Type,~R:Type,left:Declaration<S,H,L>,right:Declaration<S,H,R>) -> Declaration<S,H,L & R>:
122 match left right:
123 case Declaration{leftGrant,leftSelect,leftClauses} Declaration{rightGrant,rightSelect,rightClauses}:Declaration{(leftGrant,rightGrant),owner=>pair_select(~H,leftSelect,rightSelect,owner),List.append(&2,Clause<S>,leftClauses,rightClauses)}
124# Compose two declarations for the same schema and owner into one query.
125# Selection is conjunction, evaluated left then right with short circuiting;
126# grants retain the nested (L & R) shape and clauses retain left-to-right order.
127# Nest pair to combine more than two selections; it does not impose a row order.
128# Example: examples/scheduled-health/registration.bend defines query().
129def pair(~S:Data,~H:Type,~L:Type,~R:Type,~left:Declaration<S,H,L>,~right:Declaration<S,H,R>) -> Declaration<S,H,L & R>:
130 pair_parts(~S,~H,~L,~R,left,right)
131
132# Require this component and grant read access to its detached Data view.
133# identity and the storage binding must describe the same schema component.
134# view consumes and returns P, including affine payloads; it must not discard
135# the payload. This declaration neither visits rows nor changes the world.
136# Reads return Found, ComponentAbsent or MissingEntity (including stale/foreign).
137# Example bindings and query(): examples/scheduled-health/registration.bend.
138def read(~S:Data,~P:Type,~Store:Type,~R:Type,~E:Data,~Rest:Type,~V:Data,~Token:Data,~identity:Identity.Declaration<S,P>,~view:P -> P & V,~take:Store -> Rest & Col.Column<S,P>,~put:Rest -> Col.Column<S,P> -> Store) -> Declaration<S,Q.Frame<S,Store,R,E>,Cap.Read<Q.Frame<S,Store,R,E>,V>>:
139 Declaration{Q.read_family(~S,~Store,~R,~E,~Rest,~P,~V,~Token,~take,~put,~view,~Cmp.Family{Identity.key(~S,~P,identity)}),Q.family_match(~S,~Store,~R,~E,~Rest,~P,~V,~Token,~take,~put,~view,~Cmp.Family{Identity.key(~S,~P,identity)},~Q.Required{}),[Clause{Identity.key(~S,~P,identity),Read{}}]}
140
141# Require this component and grant typed read/write operations to the body.
142# The set operation consumes replacement P and returns the affine owner.
143# Failed sets retain an error in Frame; Compose reports AccessError and rolls back.
144# Rejected replacement payloads are not returned through this grant. Reads expose V.
145# Writes participate in the enclosing system transaction and its rollback;
146# declaring access performs no write. See scheduled-health/registration.bend.
147def write(~S:Data,~P:Type,~Store:Type,~R:Type,~E:Data,~Rest:Type,~V:Data,~Token:Data,~identity:Identity.Declaration<S,P>,~view:P -> P & V,~take:Store -> Rest & Col.Column<S,P>,~put:Rest -> Col.Column<S,P> -> Store) -> Declaration<S,Q.Frame<S,Store,R,E>,Cap.Write<Q.Frame<S,Store,R,E>,P,V>>:
148 Declaration{Q.write_family(~S,~Store,~R,~E,~Rest,~P,~V,~Token,~take,~put,~view,~Cmp.Family{Identity.key(~S,~P,identity)}),Q.family_match(~S,~Store,~R,~E,~Rest,~P,~V,~Token,~take,~put,~view,~Cmp.Family{Identity.key(~S,~P,identity)},~Q.Required{}),[Clause{Identity.key(~S,~P,identity),Write{}}]}
149
150# Grant read access without excluding rows where the component is absent.
151# The body handles Found/ComponentAbsent/MissingEntity; optional fabricates no V.
152# Stale/foreign handles yield MissingEntity and never match as live rows.
153# Payload projection consumes and returns P, preserving affine ownership.
154def optional(~S:Data,~P:Type,~Store:Type,~R:Type,~E:Data,~Rest:Type,~V:Data,~Token:Data,~identity:Identity.Declaration<S,P>,~view:P -> P & V,~take:Store -> Rest & Col.Column<S,P>,~put:Rest -> Col.Column<S,P> -> Store) -> Declaration<S,Q.Frame<S,Store,R,E>,Cap.Read<Q.Frame<S,Store,R,E>,V>>:
155 Declaration{Q.read_family(~S,~Store,~R,~E,~Rest,~P,~V,~Token,~take,~put,~view,~Cmp.Family{Identity.key(~S,~P,identity)}),Q.family_match(~S,~Store,~R,~E,~Rest,~P,~V,~Token,~take,~put,~view,~Cmp.Family{Identity.key(~S,~P,identity)},~Q.Optional{}),[Clause{Identity.key(~S,~P,identity),Optional{}}]}
156
157# Filter to entities with this component without granting its payload access.
158# Produces Unit grants; combine with pair to add independently declared access.
159def with_component(~S:Data,~P:Type,~Store:Type,~R:Type,~E:Data,~Rest:Type,~V:Data,~Token:Data,~identity:Identity.Declaration<S,P>,~view:P -> P & V,~take:Store -> Rest & Col.Column<S,P>,~put:Rest -> Col.Column<S,P> -> Store) -> Declaration<S,Q.Frame<S,Store,R,E>,Unit>:
160 Declaration{Unit{},Q.family_match(~S,~Store,~R,~E,~Rest,~P,~V,~Token,~take,~put,~view,~Cmp.Family{Identity.key(~S,~P,identity)},~Q.Present{}),[Clause{Identity.key(~S,~P,identity),With{}}]}
161
162# Filter to entities without this component, granting no payload access.
163# Membership follows the executing query and explicit structural barriers.
164def without_component(~S:Data,~P:Type,~Store:Type,~R:Type,~E:Data,~Rest:Type,~V:Data,~Token:Data,~identity:Identity.Declaration<S,P>,~view:P -> P & V,~take:Store -> Rest & Col.Column<S,P>,~put:Rest -> Col.Column<S,P> -> Store) -> Declaration<S,Q.Frame<S,Store,R,E>,Unit>:
165 Declaration{Unit{},Q.family_match(~S,~Store,~R,~E,~Rest,~P,~V,~Token,~take,~put,~view,~Cmp.Family{Identity.key(~S,~P,identity)},~Q.Absent{}),[Clause{Identity.key(~S,~P,identity),Without{}}]}
166
167# Filter by this component being added since the executing system cursor.
168# Grants no payload access; combine with read/write to access matching values.
169# Declaration creation and detached inspection do not consume lifecycle visibility.
170def added(~S:Data,~P:Type,~Store:Type,~R:Type,~E:Data,~Rest:Type,~V:Data,~Token:Data,~identity:Identity.Declaration<S,P>,~view:P -> P & V,~take:Store -> Rest & Col.Column<S,P>,~put:Rest -> Col.Column<S,P> -> Store) -> Declaration<S,Q.Frame<S,Store,R,E>,Unit>:
171 Declaration{Unit{},Q.lifecycle_match(~S,~Store,~R,~E,~Rest,~P,~V,~Token,~take,~put,~view,~Cmp.Family{Identity.key(~S,~P,identity)},~Q.Added{}),[Clause{Identity.key(~S,~P,identity),Added{}}]}
172
173# Filter by this component changing since the executing system cursor.
174# Grants no payload access; cursor advancement follows system success/skip/failure
175# rules, not creation or inspection of this declaration.
176def changed(~S:Data,~P:Type,~Store:Type,~R:Type,~E:Data,~Rest:Type,~V:Data,~Token:Data,~identity:Identity.Declaration<S,P>,~view:P -> P & V,~take:Store -> Rest & Col.Column<S,P>,~put:Rest -> Col.Column<S,P> -> Store) -> Declaration<S,Q.Frame<S,Store,R,E>,Unit>:
177 Declaration{Unit{},Q.lifecycle_match(~S,~Store,~R,~E,~Rest,~P,~V,~Token,~take,~put,~view,~Cmp.Family{Identity.key(~S,~P,identity)},~Q.Changed{}),[Clause{Identity.key(~S,~P,identity),Changed{}}]}
178
179# Declare required write access and a display function for its Data view.
180# display formats detached V for enabled debug output; it never owns P.
181# Failed sets retain an error in Frame; Compose reports AccessError and rolls back.
182# The set grant consumes P without returning rejected input; see write.
183def write_displayed(~S:Data,~P:Type,~Store:Type,~R:Type,~E:Data,~Rest:Type,~V:Data,~Token:Data,~identity:Identity.Declaration<S,P>,~view:P -> P & V,~display:V -> String,~take:Store -> Rest & Col.Column<S,P>,~put:Rest -> Col.Column<S,P> -> Store) -> Declaration<S,Q.Frame<S,Store,R,E>,Cap.Write<Q.Frame<S,Store,R,E>,P,V>>:
184 Declaration{Q.write_family_displayed(~S,~Store,~R,~E,~Rest,~P,~V,~Token,~take,~put,~view,~Cmp.Family{Identity.key(~S,~P,identity)},~display),Q.family_match(~S,~Store,~R,~E,~Rest,~P,~V,~Token,~take,~put,~view,~Cmp.Family{Identity.key(~S,~P,identity)},~Q.Required{}),[Clause{Identity.key(~S,~P,identity),Write{}}]}