git.iko.soy/rowtype-yoga/purescript-yoga-om-layer
on main help sign in
11 files changed+661−211
since splitsnapshots
unifiedsplit
.psc-ide-port+1−1 .spec-results+96−33 README.md+1 compile-fail-tests/RunLayerOpenRowMissingDep.purs+19 compile-fail-tests/WireLayersMissingDep.purs+4−4 compile-fail-tests/WireLayersWrongOrder.purs−17 spago.lock+29−11 spago.yaml+9−5 src/Yoga/Om/Layer.purs+387−140 test/Test/Yoga/Om/Layer/InstancesSpec.purs+93 test/Test/Yoga/Om/Layer/PureInferenceSpec.purs+22
.psc-ide-port+1−1
@@ -1 +1 @@
1−15887
1+15500
.spec-results+96−33
@@ -1,232 +1,295 @@
11[
2+ [
3+ "Alt falls back to second layer on failure",
4+ {
5+ "timestamp": "1775221839085.0",
6+ "success": true
7+ }
8+ ],
29 [
310 "Automatic Memoization builds the same layer only once when used in two branches",
411 {
5− "timestamp": "1771057579324.0",
12+ "timestamp": "1775221839085.0",
613 "success": true
714 }
815 ],
916 [
1017 "Automatic Memoization memoized bracketLayer acquires and releases only once",
1118 {
12− "timestamp": "1771057579324.0",
19+ "timestamp": "1775221839085.0",
1320 "success": true
1421 }
1522 ],
1623 [
1724 "Automatic Memoization memoized scoped layer runs finalizer only once",
1825 {
19− "timestamp": "1771057579324.0",
26+ "timestamp": "1775221839085.0",
2027 "success": true
2128 }
2229 ],
2330 [
2431 "Composition proves Row.Nub deduplicates shared requirements at type level",
2532 {
26− "timestamp": "1771057579324.0",
33+ "timestamp": "1775221839085.0",
2734 "success": true
2835 }
2936 ],
3037 [
3138 "Composition proves deduplication at runtime via shared Ref",
3239 {
33− "timestamp": "1771057579324.0",
40+ "timestamp": "1775221839085.0",
3441 "success": true
3542 }
3643 ],
3744 [
3845 "Error Channel composes layers with different error types",
3946 {
40− "timestamp": "1771057579324.0",
47+ "timestamp": "1775221839085.0",
4148 "success": true
4249 }
4350 ],
4451 [
4552 "Error Channel propagates errors from the first layer",
4653 {
47− "timestamp": "1771057579324.0",
54+ "timestamp": "1775221839085.0",
4855 "success": true
4956 }
5057 ],
5158 [
5259 "Error Channel propagates errors from the second layer",
5360 {
54− "timestamp": "1771057579324.0",
61+ "timestamp": "1775221839085.0",
5562 "success": true
5663 }
5764 ],
5865 [
5966 "Error Channel propagates errors through vertical composition",
6067 {
61− "timestamp": "1771057579324.0",
68+ "timestamp": "1775221839085.0",
6269 "success": true
6370 }
6471 ],
6572 [
6673 "Error Channel scoped layer error triggers finalizers",
6774 {
68− "timestamp": "1771057579324.0",
75+ "timestamp": "1775221839085.0",
6976 "success": true
7077 }
7178 ],
7279 [
7380 "Error Channel scoped layers with errors run via runScopedWith",
7481 {
75− "timestamp": "1771057579324.0",
82+ "timestamp": "1775221839085.0",
7683 "success": true
7784 }
7885 ],
7986 [
8087 "Functor / Apply / Bind applies two layers",
8188 {
82− "timestamp": "1771057579324.0",
89+ "timestamp": "1775221839085.0",
8390 "success": true
8491 }
8592 ],
8693 [
8794 "Functor / Apply / Bind binds layers sequentially",
8895 {
89− "timestamp": "1771057579324.0",
96+ "timestamp": "1775221839085.0",
9097 "success": true
9198 }
9299 ],
93100 [
94101 "Functor / Apply / Bind maps over layer output",
95102 {
96− "timestamp": "1771057579324.0",
103+ "timestamp": "1775221839085.0",
104+ "success": true
105+ }
106+ ],
107+ [
108+ "Functor / Apply / Bind runs layers against a superset context",
109+ {
110+ "timestamp": "1775221839085.0",
97111 "success": true
98112 }
99113 ],
100114 [
101115 "Layer recovering does not retry on non-matching errors",
102116 {
103− "timestamp": "1771057579324.0",
117+ "timestamp": "1775221839085.0",
104118 "success": true
105119 }
106120 ],
107121 [
108122 "Layer recovering only retries the specified error in a multi-error layer",
109123 {
110− "timestamp": "1771057579324.0",
124+ "timestamp": "1775221839085.0",
111125 "success": true
112126 }
113127 ],
114128 [
115129 "Layer recovering preserves the error when retries are exhausted",
116130 {
117− "timestamp": "1771057579324.0",
131+ "timestamp": "1775221839085.0",
118132 "success": true
119133 }
120134 ],
121135 [
122136 "Layer recovering retries layer construction on matching errors",
123137 {
124− "timestamp": "1771057579324.0",
138+ "timestamp": "1775221839085.0",
125139 "success": true
126140 }
127141 ],
128142 [
129143 "Layer recovering uses RetryStatus in the check",
130144 {
131− "timestamp": "1771057579324.0",
145+ "timestamp": "1775221839085.0",
132146 "success": true
133147 }
134148 ],
135149 [
136150 "Layer repeating repeats layer construction while condition holds",
137151 {
138− "timestamp": "1771057579324.0",
152+ "timestamp": "1775221839085.0",
139153 "success": true
140154 }
141155 ],
142156 [
143157 "Layer repeating stops when policy exhausted",
144158 {
145− "timestamp": "1771057579324.0",
159+ "timestamp": "1775221839085.0",
160+ "success": true
161+ }
162+ ],
163+ [
164+ "MonadAsk / MonadReader ask reads the context directly",
165+ {
166+ "timestamp": "1775221839085.0",
167+ "success": true
168+ }
169+ ],
170+ [
171+ "MonadAsk / MonadReader local modifies context for a layer",
172+ {
173+ "timestamp": "1775221839085.0",
174+ "success": true
175+ }
176+ ],
177+ [
178+ "MonadEffect / MonadAff liftEffect works directly in layer bind",
179+ {
180+ "timestamp": "1775221839085.0",
181+ "success": true
182+ }
183+ ],
184+ [
185+ "MonadThrow / MonadError throwError raises into the error channel",
186+ {
187+ "timestamp": "1775221839085.0",
146188 "success": true
147189 }
148190 ],
149191 [
150192 "Parallel runs layers in parallel via parApply",
151193 {
152− "timestamp": "1771057579324.0",
194+ "timestamp": "1775221839085.0",
153195 "success": true
154196 }
155197 ],
156198 [
157199 "Scoped Layers combines scoped and non-scoped layers",
158200 {
159− "timestamp": "1771057579324.0",
201+ "timestamp": "1775221839085.0",
160202 "success": true
161203 }
162204 ],
163205 [
164206 "Scoped Layers fresh layer builds twice even when same base layer",
165207 {
166− "timestamp": "1771057579324.0",
208+ "timestamp": "1775221839085.0",
167209 "success": true
168210 }
169211 ],
170212 [
171213 "Scoped Layers runs finalizers in reverse order on success",
172214 {
173− "timestamp": "1771057579324.0",
215+ "timestamp": "1775221839085.0",
174216 "success": true
175217 }
176218 ],
177219 [
178220 "Scoped Layers runs finalizers when the callback throws",
179221 {
180− "timestamp": "1771057579324.0",
222+ "timestamp": "1775221839085.0",
181223 "success": true
182224 }
183225 ],
184226 [
185227 "Scoped Layers threads finalizers through vertical composition (provide)",
186228 {
187− "timestamp": "1771057579324.0",
229+ "timestamp": "1775221839085.0",
188230 "success": true
189231 }
190232 ],
191233 [
192234 "Scoped Layers works with bracketLayer",
193235 {
194− "timestamp": "1771057579324.0",
236+ "timestamp": "1775221839085.0",
237+ "success": true
238+ }
239+ ],
240+ [
241+ "Semigroup appends layer outputs",
242+ {
243+ "timestamp": "1775221839085.0",
244+ "success": true
245+ }
246+ ],
247+ [
248+ "combinePure combines a plain record with a layer without needing VTA",
249+ {
250+ "timestamp": "1775214062703.0",
195251 "success": true
196252 }
197253 ],
198254 [
199255 "wireLayers memoizes shared dependencies within wireLayers",
200256 {
201− "timestamp": "1771057579324.0",
257+ "timestamp": "1775221839085.0",
202258 "success": true
203259 }
204260 ],
205261 [
206262 "wireLayers wireLayersDebug emits dependency graph as compiler warning",
207263 {
208− "timestamp": "1771057579324.0",
264+ "timestamp": "1775221839085.0",
209265 "success": true
210266 }
211267 ],
212268 [
213269 "wireLayers wires a diamond dependency",
214270 {
215− "timestamp": "1771057579324.0",
271+ "timestamp": "1775221839085.0",
216272 "success": true
217273 }
218274 ],
219275 [
220276 "wireLayers wires a simple linear chain (A -> B -> C)",
221277 {
222− "timestamp": "1771057579324.0",
278+ "timestamp": "1775221839085.0",
223279 "success": true
224280 }
225281 ],
226282 [
227283 "wireLayers wires independent layers with no inter-dependencies",
228284 {
229− "timestamp": "1771057579324.0",
285+ "timestamp": "1775221839085.0",
286+ "success": true
287+ }
288+ ],
289+ [
290+ "withValue combines a plain record with a layer without needing VTA",
291+ {
292+ "timestamp": "1775221839085.0",
230293 "success": true
231294 }
232295 ]
README.md+1
@@ -240,3 +240,4 @@ combineRequirements (OmLayer build1) (OmLayer build2) = makeLayer do
240240
241241**Status**: Proof of concept complete ✓ with working runtime implementation
242242**Next**: Implement full instance-resolution based layer system with provideLayers API
243+
compile-fail-tests/RunLayerOpenRowMissingDep.pursnew file+19
@@ -0,0 +1,19 @@
1+-- EXPECT: Prim.RowList.RowToList
2+module Yoga.Om.Layer.CompileFailTest where
3+
4+import Prelude
5+
6+import Yoga.Om as Om
7+import Yoga.Om.Layer (OmLayer, makeLayer, runLayer)
8+
9+type Fastify = String
10+type Connection = String
11+
12+apiLayer :: forall r. OmLayer (fastify :: Fastify, sqlite :: Connection | r) () {}
13+apiLayer = makeLayer do
14+ { fastify, sqlite } <- Om.ask
15+ let _ = fastify <> sqlite
16+ pure {}
17+
18+bad :: Om.Om { fastify :: Fastify } () {}
19+bad = runLayer { fastify: "server" } apiLayer
compile-fail-tests/WireLayersMissingDep.purs+4−4
@@ -1,4 +1,4 @@
1−-- EXPECT: TypesDoNotUnify
1+-- EXPECT: Dependency not provided!
22module Yoga.Om.Layer.CompileFailTest where
33
44import Prelude
@@ -8,9 +8,9 @@ import Yoga.Om.Layer (OmLayer, Scope, makeLayer, wireLayers)
88a_base :: OmLayer (scope :: Scope) () { base :: String }
99a_base = makeLayer (pure { base: "ok" })
1010
11−b_upper :: OmLayer (scope :: Scope, missing :: Int) () { upper :: String }
11+b_upper :: OmLayer (scope :: Scope, port :: Int) () { upper :: String }
1212b_upper = makeLayer do
13− { missing } <- Om.ask
14− pure { upper: show missing }
13+ { port } <- Om.ask
14+ pure { upper: show port }
1515
1616bad = wireLayers { a_base, b_upper }
compile-fail-tests/WireLayersWrongOrder.pursdeleted−17
@@ -1,17 +0,0 @@
1−-- EXPECT: TypesDoNotUnify
2−module Yoga.Om.Layer.CompileFailTest where
3−
4−import Prelude
5−import Yoga.Om as Om
6−import Yoga.Om.Layer (OmLayer, Scope, makeLayer, wireLayers)
7−
8−-- a_base needs upper, but b_upper provides it — wrong alphabetical order
9−a_needs_upper :: OmLayer (scope :: Scope, upper :: String) () { final :: String }
10−a_needs_upper = makeLayer do
11− { upper } <- Om.ask
12− pure { final: upper <> "!" }
13−
14−b_upper :: OmLayer (scope :: Scope) () { upper :: String }
15−b_upper = makeLayer (pure { upper: "hello" })
16−
17−bad = wireLayers { a_needs_upper, b_upper }
spago.lock+29−11
@@ -6,7 +6,7 @@
66 "core": {
77 "dependencies": [
88 {
9− "aff": ">=7.1.0 <8.0.0"
9+ "aff": ">=8.0.0 <9.0.0"
1010 },
1111 {
1212 "aff-retry": ">=2.0.0 <3.0.0"
@@ -14,6 +14,9 @@
1414 {
1515 "arrays": ">=7.3.0 <8.0.0"
1616 },
17+ {
18+ "control": ">=6.0.0 <7.0.0"
19+ },
1720 {
1821 "effect": ">=4.0.0 <5.0.0"
1922 },
@@ -23,9 +26,6 @@
2326 {
2427 "foldable-traversable": ">=6.0.0 <7.0.0"
2528 },
26− {
27− "integers": ">=6.0.0 <7.0.0"
28− },
2929 {
3030 "maybe": ">=6.0.0 <7.0.0"
3131 },
@@ -35,6 +35,9 @@
3535 {
3636 "ordered-collections": ">=3.2.0 <4.0.0"
3737 },
38+ {
39+ "parallel": ">=7.0.0 <8.0.0"
40+ },
3841 {
3942 "prelude": ">=6.0.2 <7.0.0"
4043 },
@@ -44,9 +47,21 @@
4447 {
4548 "record-studio": ">=1.0.4 <2.0.0"
4649 },
50+ {
51+ "refs": ">=6.0.0 <7.0.0"
52+ },
53+ {
54+ "tailrec": ">=6.1.0 <7.0.0"
55+ },
56+ {
57+ "transformers": ">=6.1.0 <7.0.0"
58+ },
4759 {
4860 "type-equality": ">=4.0.1 <5.0.0"
4961 },
62+ {
63+ "typelevel-prelude": ">=7.0.0 <8.0.0"
64+ },
5065 {
5166 "unsafe-coerce": ">=6.0.0 <7.0.0"
5267 },
@@ -54,7 +69,7 @@
5469 "variant": ">=8.0.0 <9.0.0"
5570 },
5671 {
57− "yoga-om": ">=1.2.2 <2.0.0"
72+ "yoga-om": ">=2.0.0 <3.0.0"
5873 }
5974 ]
6075 },
@@ -84,8 +99,8 @@
8499 "packages": {
85100 "aff": {
86101 "type": "registry",
87− "version": "7.1.0",
88− "integrity": "sha256-/nOpDQVnKIoP1pQ1ZZBYpadK0TuMlG4WwufGzJpsfD0=",
102+ "version": "8.0.0",
103+ "integrity": "sha256-9BRIrSdRoYybfiUmBqgKJ66cPqxYHb277bVWQMn/9sg=",
89104 "dependencies": [
90105 "control",
91106 "datetime",
@@ -989,8 +1004,8 @@
9891004 },
9901005 "spec": {
9911006 "type": "registry",
992− "version": "8.1.1",
993− "integrity": "sha256-b6qjDb7lUJGLV+WEoywLu6BCXC4zGpfbN43mTL+vBeI=",
1007+ "version": "8.1.2",
1008+ "integrity": "sha256-klPPJ6lBQI1CGOAdLJ+ZoGRt8XZippkTKjn53BrSkL0=",
9941009 "dependencies": [
9951010 "aff",
9961011 "ansi",
@@ -1216,10 +1231,11 @@
12161231 },
12171232 "yoga-om": {
12181233 "type": "registry",
1219− "version": "1.2.2",
1220− "integrity": "sha256-uDV3/o8mUOTIgSEQFqIATPyladzL8BcjURvfS901hgI=",
1234+ "version": "2.0.0",
1235+ "integrity": "sha256-eenoujD2rJzR6gYJbbIiqLhXHC1C5dagu91HZkAEt/Y=",
12211236 "dependencies": [
12221237 "aff",
1238+ "aff-retry",
12231239 "avar",
12241240 "console",
12251241 "control",
@@ -1228,12 +1244,14 @@
12281244 "either",
12291245 "exceptions",
12301246 "functions",
1247+ "heterogeneous",
12311248 "maybe",
12321249 "newtype",
12331250 "parallel",
12341251 "prelude",
12351252 "record",
12361253 "record-studio",
1254+ "refs",
12371255 "tailrec",
12381256 "transformers",
12391257 "tuples",
spago.yaml+9−5
@@ -1,31 +1,35 @@
11workspace: {}
2−
32package:
43 name: yoga-om-layer
54 publish:
6− version: 1.0.6
5+ version: 2.1.0
76 license: MIT
87 location:
98 githubOwner: rowtype-yoga
109 githubRepo: purescript-yoga-om-layer
1110 dependencies:
12− - aff: ">=7.1.0 <8.0.0"
11+ - aff: ">=8.0.0 <9.0.0"
1312 - aff-retry: ">=2.0.0 <3.0.0"
1413 - arrays: ">=7.3.0 <8.0.0"
14+ - control: ">=6.0.0 <7.0.0"
1515 - effect: ">=4.0.0 <5.0.0"
1616 - exceptions: ">=6.1.0 <7.0.0"
1717 - foldable-traversable: ">=6.0.0 <7.0.0"
18− - integers: ">=6.0.0 <7.0.0"
1918 - maybe: ">=6.0.0 <7.0.0"
2019 - nullable: ">=6.0.0 <7.0.0"
2120 - ordered-collections: ">=3.2.0 <4.0.0"
21+ - parallel: ">=7.0.0 <8.0.0"
2222 - prelude: ">=6.0.2 <7.0.0"
2323 - record: ">=4.0.0 <5.0.0"
2424 - record-studio: ">=1.0.4 <2.0.0"
25+ - refs: ">=6.0.0 <7.0.0"
26+ - tailrec: ">=6.1.0 <7.0.0"
27+ - transformers: ">=6.1.0 <7.0.0"
2528 - type-equality: ">=4.0.1 <5.0.0"
29+ - typelevel-prelude: ">=7.0.0 <8.0.0"
2630 - unsafe-coerce: ">=6.0.0 <7.0.0"
2731 - variant: ">=8.0.0 <9.0.0"
28− - yoga-om: ">=1.2.2 <2.0.0"
32+ - yoga-om: ">=2.0.0 <3.0.0"
2933 test:
3034 main: Test.Main
3135 dependencies:
src/Yoga/Om/Layer.purs+387−140
@@ -13,40 +13,59 @@ module Yoga.Om.Layer
1313 , runScopedWith
1414 , withScoped
1515 , withScopedWith
16+ , scoped
1617 , combineRequirements
18+ , withValue
1719 , provide
1820 , (>->)
1921 , recovering
2022 , repeating
2123 , wireLayers
2224 , wireLayersDebug
23− , wireLayersRL
24− , class WireLayersRL
25− , class RowLabels
2625 , class FilterScope
2726 , class FindProvider
2827 , class FindProviderMatch
2928 , class HasLabel
30− , class LayerDepth
29+ , class EmitEdges
30+ , class DashesFor
31+ , class BoxTop, class BoxMid, class BoxBot
32+ , class RenderDepChildNode
33+ , class IsNotProvided
34+ , class HasDirectMissing, class HasDirectMissingOr
3135 , class FindLayerReqs
32− , class MaxProviderDepth
33− , class MaxSymbol
36+ , class RenderProviderDeps
37+ , class RenderDepTree
38+ , class FirstProvLabel
39+ , class PrintLayerDispatch
3440 , class PrintLayersRL
3541 , class PrintEdgesRL
36− , class IsNil
37− , class FormatNode
38− , class ProviderNames
42+ , class PrintLayersASCII
43+ , class PrintEdgesASCII
44+ , NotProvided
3945 , class CheckAllProvided
4046 , class CheckAllLabelsExist
4147 , class CheckLabelExists
48+ , class TopoWire, topoWire
49+ , class AllLabelsIn
50+ , class FindReady
51+ , class FindReadyDispatch
4252 ) where
4353
4454import Prelude
4555
46−import Control.Monad.Error.Class (catchError, throwError)
47−import Data.Symbol (class IsSymbol)
56+import Control.Alt (class Alt)
57+import Control.Alt as Alt
58+import Control.Alternative (class Alternative)
59+import Control.Monad.Error.Class (class MonadError, class MonadThrow, catchError, throwError)
60+import Control.Monad.Reader (class MonadAsk, class MonadReader)
61+import Control.Monad.Reader as Reader
62+import Control.Monad.Rec.Class (class MonadRec, Step(..), tailRecM)
4863import Control.Parallel (class Parallel)
4964import Control.Parallel as Parallel
65+import Control.Plus (class Plus)
66+import Control.Plus as Plus
67+import Data.Symbol (class IsSymbol)
68+import Data.Variant (Variant)
5069import Data.Array as Array
5170import Data.Foldable (for_)
5271import Data.Map (Map)
@@ -57,7 +76,10 @@ import Data.Variant (class VariantMatchCases, onMatch)
5776import Effect (Effect)
5877import Effect.Aff (Aff, bracket)
5978import Effect.Aff.Retry (RetryPolicyM, RetryStatus, applyAndDelay, defaultRetryStatus)
60−import Effect.Class (liftEffect)
79+import Effect.Aff.Class (class MonadAff)
80+import Effect.Aff.Class as Aff
81+import Effect.Class (class MonadEffect, liftEffect)
82+import Effect.Class (class MonadEffect, liftEffect) as Effect
6183import Effect.Exception (Error)
6284import Effect.Ref (Ref)
6385import Effect.Ref as Ref
@@ -67,14 +89,17 @@ import Prim.Row as Row
6789import Type.Proxy (Proxy(..))
6890import Prim.RowList (class RowToList, Cons, Nil, RowList)
6991import Prim.Boolean (True, False)
70−import Prim.Symbol (class Append)
71−import Prim.TypeError (class Fail, class Warn, Above, Doc, Quote, Text)
92+import Prim.Symbol (class Append, class Cons)
93+import Type.Data.Boolean (class And)
94+import Type.RowList (class RowListAppend)
95+import Prim.TypeError (class Fail, class Warn, Above, Beside, Doc, Quote, Text)
7296import Record as Record
7397import Record.Studio (class Keys)
7498import Type.Equality (class TypeEquals)
7599import Unsafe.Coerce (unsafeCoerce)
76100import Yoga.Om (Om, ParOm)
77101import Yoga.Om as Om
102+import Yoga.Om.Error (Exception)
78103
79104-- =============================================================================
80105-- Scope — a first-class representation of resource lifetime
@@ -163,6 +188,36 @@ instance Bind (OmLayer req err) where
163188
164189instance Monad (OmLayer req err)
165190
191+instance MonadEffect (OmLayer req err) where
192+ liftEffect eff = OmLayer (freshId unit) (Effect.liftEffect eff)
193+
194+instance MonadAff (OmLayer req err) where
195+ liftAff aff = OmLayer (freshId unit) (Aff.liftAff aff)
196+
197+instance MonadAsk (Record req) (OmLayer req err) where
198+ ask = OmLayer (freshId unit) Reader.ask
199+
200+instance MonadReader (Record req) (OmLayer req err) where
201+ local f layer = OmLayer (freshId unit) (Reader.local f (buildLayer layer))
202+
203+instance MonadThrow (Variant (Exception err)) (OmLayer req err) where
204+ throwError e = OmLayer (freshId unit) (throwError e)
205+
206+instance MonadError (Variant (Exception err)) (OmLayer req err) where
207+ catchError layer handler = OmLayer (freshId unit) (catchError (buildLayer layer) (\e -> buildLayer (handler e)))
208+
209+instance MonadRec (OmLayer req err) where
210+ tailRecM f a = OmLayer (freshId unit) (tailRecM (\x -> buildLayer (f x)) a)
211+
212+instance Semigroup a => Semigroup (OmLayer req err a) where
213+ append a b = OmLayer (freshId unit) (append (buildLayer a) (buildLayer b))
214+
215+instance Alt (OmLayer req err) where
216+ alt a b = OmLayer (freshId unit) (Alt.alt (buildLayer a) (buildLayer b))
217+
218+instance Plus (OmLayer req err) where
219+ empty = OmLayer (freshId unit) Plus.empty
220+
166221instance Functor (ParOmLayer req err) where
167222 map f (ParOmLayer p) = ParOmLayer (map f p)
168223
@@ -172,6 +227,14 @@ instance Apply (ParOmLayer req err) where
172227instance Applicative (ParOmLayer req err) where
173228 pure a = ParOmLayer (pure a)
174229
230+instance Alt (ParOmLayer req err) where
231+ alt (ParOmLayer a) (ParOmLayer b) = ParOmLayer (Alt.alt a b)
232+
233+instance Plus (ParOmLayer req err) where
234+ empty = ParOmLayer Plus.empty
235+
236+instance Alternative (ParOmLayer req err)
237+
175238instance Parallel (ParOmLayer req err) (OmLayer req err) where
176239 parallel (OmLayer _ om) = ParOmLayer (Parallel.parallel om)
177240 sequential (ParOmLayer p) = OmLayer (freshId unit) (Parallel.sequential p)
@@ -329,9 +392,13 @@ else instance
329392-- =============================================================================
330393
331394-- | Run a layer with a given context, with custom error if requirements aren't met.
395+-- | `runLayer` requires a closed requirement row. If you have an open layer like
396+-- | `OmLayer (db :: Db | r) err a`, specialize it to a closed row before running
397+-- | it directly.
332398runLayer
333399 :: forall req err a available
334400 . CheckAllProvided req available
401+ => Keys req
335402 => Record available
336403 -> OmLayer req err a
337404 -> Om (Record available) err a
@@ -359,7 +426,7 @@ withScoped
359426 . OmLayer (scope :: Scope) () (Record prov)
360427 -> (Record prov -> Aff a)
361428 -> Aff a
362−withScoped = withScopedWith { exception: \_ -> pure (unsafeCoerce {}) }
429+withScoped = withScopedWith { exception: throwError }
363430
364431-- | Like `runScoped` but accepts error handlers for layers with typed errors.
365432runScopedWith
@@ -392,6 +459,17 @@ withScopedWith handlers layer callback = bracket acquire release use
392459 release { scope } = closeScope scope
393460 use { prov } = callback prov
394461
462+scoped
463+ :: forall prov a r rl err_ err
464+ . RowToList (exception :: Error -> Aff (Record prov) | r) rl
465+ => VariantMatchCases rl err_ (Aff (Record prov))
466+ => Union err_ () (exception :: Error | err)
467+ => { exception :: Error -> Aff (Record prov) | r }
468+ -> OmLayer (scope :: Scope) err (Record prov)
469+ -> (Record prov -> Aff a)
470+ -> Aff a
471+scoped = withScopedWith
472+
395473-- =============================================================================
396474-- Horizontal composition — combine two layers with automatic deduplication
397475-- =============================================================================
@@ -418,6 +496,17 @@ combineRequirements layer1 layer2 = OmLayer (freshId unit) do
418496 rec2 <- Om.expand (buildLayer layer2)
419497 pure (Record.merge rec1 rec2)
420498
499+withValue
500+ :: forall req err prov1 prov2 provMerged
501+ . Union prov1 prov2 provMerged
502+ => Nub provMerged provMerged
503+ => Record prov1
504+ -> OmLayer req err (Record prov2)
505+ -> OmLayer req err (Record provMerged)
506+withValue defaults layer = OmLayer (freshId unit) do
507+ rec <- buildLayer layer
508+ pure (Record.merge defaults rec)
509+
421510-- =============================================================================
422511-- Vertical composition — feed output of one layer into input of another
423512-- =============================================================================
@@ -488,13 +577,82 @@ repeating policy shouldRepeat (OmLayer layerId om) =
488577 (applyAndDelay policy status >>= maybe (pure result) go)
489578 (pure result)
490579
580+
491581-- =============================================================================
492−-- Auto-wiring
582+-- Topological auto-wiring (order-independent)
493583-- =============================================================================
494584
495−class
496− WireLayersRL
497− (rl :: RowList Type)
585+-- | Check if ALL labels in `needles` exist in `haystack`, returning a Boolean.
586+class AllLabelsIn (needles :: RowList Type) (haystack :: RowList Type) (result :: Boolean)
587+ | needles haystack -> result
588+
589+instance AllLabelsIn Nil haystack True
590+
591+instance
592+ ( HasLabel label haystack found
593+ , AllLabelsIn tail haystack tailResult
594+ , And found tailResult result
595+ ) =>
596+ AllLabelsIn (Cons label ty tail) haystack result
597+
598+-- | Dispatch based on whether a layer is ready (deps satisfied).
599+class FindReadyDispatch
600+ (ready :: Boolean)
601+ (sym :: Symbol)
602+ (layer :: Type)
603+ (tail :: RowList Type)
604+ (available :: RowList Type)
605+ (allLayersRL :: RowList Type)
606+ (foundSym :: Symbol)
607+ (foundLayer :: Type)
608+ (rest :: RowList Type)
609+ | ready sym layer tail available allLayersRL -> foundSym foundLayer rest
610+
611+instance FindReadyDispatch True sym layer tail available allLayersRL sym layer tail
612+
613+else instance
614+ FindReady tail available allLayersRL foundSym foundLayer rest' =>
615+ FindReadyDispatch False sym layer tail available allLayersRL foundSym foundLayer (Cons sym layer rest')
616+
617+-- | Scan candidates for the first layer whose requirements are satisfied.
618+class FindReady
619+ (candidates :: RowList Type)
620+ (available :: RowList Type)
621+ (allLayersRL :: RowList Type)
622+ (sym :: Symbol)
623+ (layer :: Type)
624+ (rest :: RowList Type)
625+ | candidates available allLayersRL -> sym layer rest
626+
627+instance
628+ ( PrintLayersASCII allLayersRL graphDoc
629+ , Fail
630+ ( Above (Text "")
631+ ( Above (Text "Dependency not provided!")
632+ ( Above (Text "")
633+ ( Above (Text "No remaining layer has all its dependencies satisfied.")
634+ ( Above (Text "")
635+ ( Above (Text "Dependency graph:") graphDoc
636+ )
637+ )
638+ )
639+ )
640+ )
641+ )
642+ ) =>
643+ FindReady Nil available allLayersRL sym layer rest
644+
645+else instance
646+ ( RowToList req reqRL
647+ , AllLabelsIn reqRL available isReady
648+ , FindReadyDispatch isReady sym (OmLayer req err (Record prov)) tail available allLayersRL foundSym foundLayer rest
649+ ) =>
650+ FindReady (Cons sym (OmLayer req err (Record prov)) tail) available allLayersRL foundSym foundLayer rest
651+
652+-- | Topologically sort and wire layers. Each iteration finds a layer whose
653+-- | dependencies are already satisfied, processes it, and adds its provisions.
654+class TopoWire
655+ (remaining :: RowList Type)
498656 (layers :: Row Type)
499657 (accReq :: Row Type)
500658 (accErr :: Row Type)
@@ -502,20 +660,25 @@ class
502660 (resReq :: Row Type)
503661 (resErr :: Row Type)
504662 (resProv :: Row Type)
505− | rl layers accReq accErr accProv -> resReq resErr resProv
663+ | remaining layers accReq accErr accProv -> resReq resErr resProv
506664 where
507− wireLayersRL
508− :: Proxy rl
665+ topoWire
666+ :: Proxy remaining
509667 -> Record layers
510668 -> OmLayer accReq accErr (Record accProv)
511669 -> OmLayer resReq resErr (Record resProv)
512670
513−instance WireLayersRL Nil layers accReq accErr accProv accReq accErr accProv where
514− wireLayersRL _ _ acc = acc
671+instance TopoWire Nil layers accReq accErr accProv accReq accErr accProv where
672+ topoWire _ _ acc = acc
515673
516−instance
517− ( IsSymbol sym
518− , Row.Cons sym (OmLayer nextReq nextErr (Record nextProv)) _rest layers
674+else instance
675+ ( RowToList layers allLayersRL
676+ , RowToList accProv accProvRL
677+ , RowToList accReq accReqRL
678+ , RowListAppend accProvRL accReqRL availableRL
679+ , FindReady remaining availableRL allLayersRL readySym (OmLayer nextReq nextErr (Record nextProv)) rest
680+ , IsSymbol readySym
681+ , Row.Cons readySym (OmLayer nextReq nextErr (Record nextProv)) _rest layers
519682 -- next's requirements must be subset of (accProv + accReq)
520683 , Union accProv accReq provReqOut
521684 , Nub provReqOut provReqOut
@@ -528,19 +691,18 @@ instance
528691 , Nub errMergedRaw errMerged
529692 , Union accErr _e1 errMerged
530693 , Union nextErr _e2 errMerged
531− -- merge provisions (Nub provMerged provMerged ensures disjoint)
694+ -- merge provisions
532695 , Union accProv nextProv provMerged
533696 , Nub provMerged provMerged
534697 -- recurse
535− , WireLayersRL tail layers accReq errMerged provMerged resReq resErr resProv
698+ , TopoWire rest layers accReq errMerged provMerged resReq resErr resProv
536699 ) =>
537− WireLayersRL (Cons sym (OmLayer nextReq nextErr (Record nextProv)) tail) layers accReq accErr accProv resReq resErr resProv
538− where
539− wireLayersRL _ layers acc =
540− wireLayersRL (Proxy :: Proxy tail) layers combined
700+ TopoWire remaining layers accReq accErr accProv resReq resErr resProv where
701+ topoWire _ layers acc =
702+ topoWire (Proxy :: Proxy rest) layers combined
541703 where
542704 next :: OmLayer nextReq nextErr (Record nextProv)
543− next = Record.get (Proxy :: Proxy sym) layers
705+ next = Record.get (Proxy :: Proxy readySym) layers
544706
545707 combined :: OmLayer accReq errMerged (Record provMerged)
546708 combined = OmLayer (freshId unit) do
@@ -551,33 +713,18 @@ instance
551713wireLayers
552714 :: forall layers rl req err prov
553715 . RowToList layers rl
554− => WireLayersRL rl layers (scope :: Scope) () () req err prov
716+ => TopoWire rl layers (scope :: Scope) () () req err prov
555717 => Record layers
556718 -> OmLayer req err (Record prov)
557719wireLayers layers =
558− wireLayersRL (Proxy :: Proxy rl) layers seed
720+ topoWire (Proxy :: Proxy rl) layers seed
559721 where
560722 seed = pure {} :: OmLayer (scope :: Scope) () (Record ())
561723
562724-- =============================================================================
563−-- Debug graph printing
725+-- Debug graph printing (D2 diagram)
564726-- =============================================================================
565727
566−-- | Collect labels from a RowList into a comma-separated Symbol.
567−class RowLabels (rl :: RowList Type) (out :: Symbol) | rl -> out
568−
569−instance RowLabels Nil ""
570−
571−else instance IsSymbol sym => RowLabels (Cons sym ty Nil) sym
572−
573−else instance
574− ( IsSymbol sym
575− , RowLabels tail rest
576− , Append sym ", " symComma
577− , Append symComma rest out
578− ) =>
579− RowLabels (Cons sym ty tail) out
580−
581728-- | Remove `scope :: Scope` from a RowList (it's always present, just noise).
582729class FilterScope (rl :: RowList Type) (out :: RowList Type) | rl -> out
583730
@@ -585,11 +732,16 @@ instance FilterScope Nil Nil
585732instance FilterScope (Cons "scope" Scope tail) tail
586733else instance FilterScope tail out => FilterScope (Cons sym ty tail) (Cons sym ty out)
587734
735+-- | Marker symbol for unresolved dependencies.
736+type NotProvided :: Symbol
737+type NotProvided = "???"
738+
588739-- | Given a label, find which layer in the RowList provides it.
589740class FindProvider (label :: Symbol) (rl :: RowList Type) (provider :: Symbol) | label rl -> provider
590741
591−-- This layer provides the label
592−instance
742+instance FindProvider label Nil NotProvided
743+
744+else instance
593745 ( RowToList prov provRL
594746 , HasLabel label provRL hasIt
595747 , FindProviderMatch hasIt label sym (Cons sym (OmLayer req err (Record prov)) tail) provider
@@ -599,10 +751,8 @@ instance
599751-- | Dispatch based on whether the current layer has the label.
600752class FindProviderMatch (hasIt :: Boolean) (label :: Symbol) (sym :: Symbol) (rl :: RowList Type) (provider :: Symbol) | hasIt label sym rl -> provider
601753
602−-- Yes: this layer provides it
603754instance FindProviderMatch True label sym rl sym
604755
605−-- No: keep looking in the tail
606756else instance
607757 FindProvider label tail provider =>
608758 FindProviderMatch False label sym (Cons sym ty tail) provider
@@ -614,137 +764,234 @@ instance HasLabel label Nil False
614764instance HasLabel label (Cons label ty tail) True
615765else instance HasLabel label tail result => HasLabel label (Cons sym ty tail) result
616766
617−-- | Compute depth of a layer: roots = "", each level adds " ".
618−class LayerDepth (sym :: Symbol) (allLayers :: RowList Type) (depth :: Symbol) | sym allLayers -> depth
767+-- | Emit D2 edges for one consumer: "provider -> consumer" per requirement.
768+class EmitEdges (consumer :: Symbol) (reqRL :: RowList Type) (allLayers :: RowList Type) (doc :: Doc) | consumer reqRL allLayers -> doc
619769
620−instance
621− ( FindLayerReqs sym allLayers reqRL
622− , FilterScope reqRL filteredReqRL
623− , MaxProviderDepth filteredReqRL allLayers depth
770+instance EmitEdges consumer Nil allLayers (Text "")
771+
772+else instance
773+ ( FindProvider label allLayers provider
774+ , Append " " provider s0
775+ , Append s0 " --> " s1
776+ , Append s1 consumer line
777+ , EmitEdges consumer tail allLayers restDoc
624778 ) =>
625− LayerDepth sym allLayers depth
779+ EmitEdges consumer (Cons label ty tail) allLayers (Above (Text line) restDoc)
780+
781+-- | Walk all layers emitting D2 edges (mermaid format for wireLayersDebug).
782+class PrintLayersRL (rl :: RowList Type) (doc :: Doc) | rl -> doc
783+class PrintEdgesRL (rl :: RowList Type) (allLayers :: RowList Type) (doc :: Doc) | rl allLayers -> doc
784+
785+instance PrintLayersRL Nil (Text "")
786+else instance PrintEdgesRL rl rl doc => PrintLayersRL rl doc
626787
627−-- | Find the requirements RowList for a named layer.
628−class FindLayerReqs (sym :: Symbol) (rl :: RowList Type) (reqRL :: RowList Type) | sym rl -> reqRL
788+instance PrintEdgesRL Nil allLayers (Text "")
629789
630−instance
790+else instance
631791 ( RowToList req reqRL
792+ , FilterScope reqRL filteredReqRL
793+ , EmitEdges sym filteredReqRL allLayers edgeDoc
794+ , PrintEdgesRL tail allLayers restDoc
632795 ) =>
633− FindLayerReqs sym (Cons sym (OmLayer req err (Record prov)) tail) reqRL
796+ PrintEdgesRL (Cons sym (OmLayer req err (Record prov)) tail) allLayers (Above edgeDoc restDoc)
797+
798+-- | Map a symbol to same-length dashes (for box borders).
799+class DashesFor (sym :: Symbol) (dashes :: Symbol) | sym -> dashes
800+
801+instance DashesFor "" ""
634802
635803else instance
636− FindLayerReqs sym tail reqRL =>
637− FindLayerReqs sym (Cons other ty tail) reqRL
804+ ( Cons head tail sym
805+ , DashesFor tail rest
806+ , Append "─" rest dashes
807+ ) =>
808+ DashesFor sym dashes
638809
639−-- | Compute max depth across all providers of a layer's requirements.
640−-- For each required label, find its provider, get provider's depth, add " ".
641−-- "Max" is approximated: we pick the longest depth symbol (deepest provider).
642−class MaxProviderDepth (reqRL :: RowList Type) (allLayers :: RowList Type) (depth :: Symbol) | reqRL allLayers -> depth
810+-- | Build box parts from a symbol.
811+class BoxTop (sym :: Symbol) (result :: Symbol) | sym -> result
643812
644−-- No requirements: root layer, depth ""
645−instance MaxProviderDepth Nil allLayers ""
813+instance
814+ ( DashesFor sym dashes
815+ , Append "╭─" dashes s0
816+ , Append s0 "─╮" result
817+ ) =>
818+ BoxTop sym result
646819
647−-- One or more requirements: depth = provider's depth + " "
648−else instance
649− ( FindProvider label allLayers provider
650− , LayerDepth provider allLayers provDepth
651− , Append provDepth " " nextDepth
652− , MaxProviderDepth tail allLayers tailDepth
653− , MaxSymbol nextDepth tailDepth depth
820+class BoxMid (sym :: Symbol) (result :: Symbol) | sym -> result
821+
822+instance
823+ ( Append "│ " sym s0
824+ , Append s0 " │" result
654825 ) =>
655− MaxProviderDepth (Cons label ty tail) allLayers depth
826+ BoxMid sym result
656827
657−-- | Pick the longer of two Symbols (approximating max depth).
658−class MaxSymbol (a :: Symbol) (b :: Symbol) (max :: Symbol) | a b -> max
828+class BoxBot (sym :: Symbol) (result :: Symbol) | sym -> result
659829
660−-- Both empty: result is empty
661−instance MaxSymbol "" "" ""
830+instance
831+ ( DashesFor sym dashes
832+ , Append "╰─" dashes s0
833+ , Append s0 "─╯" result
834+ ) =>
835+ BoxBot sym result
662836
663−-- a empty, b not: b wins
664−else instance MaxSymbol "" b b
837+-- | Render a single dependency child node (dispatches on provided vs missing).
838+class RenderDepChildNode
839+ (isNotProvided :: Boolean) (provider :: Symbol) (label :: Symbol) (ty :: Type)
840+ (prePrefix :: Symbol) (contPrefix :: Symbol) (branchPrefix :: Symbol)
841+ (allLayers :: RowList Type) (doc :: Doc)
842+ | isNotProvided provider label ty prePrefix contPrefix branchPrefix allLayers -> doc
665843
666−-- a not empty, b empty: a wins
667−else instance MaxSymbol a "" a
844+-- Missing dep: boxed label, type outside
845+instance
846+ ( BoxTop label topLine, BoxMid label midLine, BoxBot label botLine
847+ , Append prePrefix topLine topFull
848+ , Append branchPrefix midLine midFull
849+ , Append contPrefix botLine botFull
850+ ) =>
851+ RenderDepChildNode True provider label ty prePrefix contPrefix branchPrefix allLayers
852+ (Above (Text topFull) (Above (Beside (Text midFull) (Beside (Text " :: ") (Beside (Quote ty) (Text " <-- ⚠️ not provided")))) (Text botFull)))
668853
669−-- Both non-empty: strip one char from each and recurse, then restore the winner
670−else instance
671− ( Append " " restA a
672− , Append " " restB b
673− , MaxSymbol restA restB deeper
674− , Append " " deeper max
854+-- Provided dep: boxed label, then recurse into provider's sub-tree
855+instance
856+ ( BoxTop label topLine, BoxMid label midLine, BoxBot label botLine
857+ , Append prePrefix topLine topFull
858+ , Append branchPrefix midLine midFull
859+ , Append contPrefix botLine botFull
860+ , Append contPrefix " " subTreePrefix
861+ , RenderProviderDeps provider subTreePrefix allLayers subDoc
675862 ) =>
676− MaxSymbol a b max
863+ RenderDepChildNode False provider label ty prePrefix contPrefix branchPrefix allLayers
864+ (Above (Text topFull) (Above (Text midFull) (Above (Text botFull) subDoc)))
677865
678−-- | Walk the layers RowList, printing each with indentation.
679−class PrintLayersRL (rl :: RowList Type) (doc :: Doc) | rl -> doc
680−class PrintEdgesRL (rl :: RowList Type) (allLayers :: RowList Type) (doc :: Doc) | rl allLayers -> doc
866+-- | Is this provider the NotProvided marker?
867+class IsNotProvided (provider :: Symbol) (result :: Boolean) | provider -> result
868+instance IsNotProvided NotProvided True
869+else instance IsNotProvided other False
681870
682−instance PrintLayersRL Nil (Text "")
683−else instance PrintEdgesRL rl rl doc => PrintLayersRL rl doc
871+-- | Does any requirement in this RowList resolve to NotProvided?
872+class HasDirectMissing (reqRL :: RowList Type) (allLayers :: RowList Type) (result :: Boolean)
873+ | reqRL allLayers -> result
684874
685−instance PrintEdgesRL Nil allLayers (Text "")
875+instance HasDirectMissing Nil allLayers False
686876
687877else instance
688− ( RowToList req reqRL
689− , FilterScope reqRL filteredReqRL
690− , LayerDepth sym allLayers depth
691− , IsNil filteredReqRL isRoot
692− , FormatNode isRoot depth sym filteredReqRL allLayers line
693− , PrintEdgesRL tail allLayers restDoc
878+ ( FindProvider label allLayers provider
879+ , IsNotProvided provider isMissing
880+ , HasDirectMissingOr isMissing tail allLayers result
694881 ) =>
695− PrintEdgesRL (Cons sym (OmLayer req err (Record prov)) tail) allLayers (Above (Text line) restDoc)
882+ HasDirectMissing (Cons label ty tail) allLayers result
883+
884+class HasDirectMissingOr (found :: Boolean) (tail :: RowList Type) (allLayers :: RowList Type) (result :: Boolean)
885+ | found tail allLayers -> result
696886
697−-- | Check if a RowList is Nil.
698−class IsNil (rl :: RowList Type) (result :: Boolean) | rl -> result
887+instance HasDirectMissingOr True tail allLayers True
699888
700−instance IsNil Nil True
701−else instance IsNil (Cons sym ty tail) False
889+else instance
890+ HasDirectMissing tail allLayers result =>
891+ HasDirectMissingOr False tail allLayers result
702892
703−-- | Format a node: root gets ○, others get ◀── providerList
704−class FormatNode (isRoot :: Boolean) (depth :: Symbol) (sym :: Symbol) (reqRL :: RowList Type) (allLayers :: RowList Type) (line :: Symbol) | isRoot depth sym reqRL allLayers -> line
893+-- | Look up a layer's requirements by name.
894+class FindLayerReqs (sym :: Symbol) (allLayers :: RowList Type) (reqRL :: RowList Type)
895+ | sym allLayers -> reqRL
705896
706−-- Root node
707897instance
708− ( Append " " depth s1
709− , Append s1 "○ " s2
710− , Append s2 sym line
711− ) =>
712− FormatNode True depth sym Nil allLayers line
898+ RowToList req reqRL =>
899+ FindLayerReqs sym (Cons sym (OmLayer req err (Record prov)) tail) reqRL
713900
714−-- Non-root: show providers ───▶ this
715901else instance
716− ( ProviderNames reqRL allLayers provSym
717− , Append " " depth s1
718− , Append s1 provSym s2
719− , Append s2 " ───▶ " s3
720− , Append s3 sym line
902+ FindLayerReqs sym tail reqRL =>
903+ FindLayerReqs sym (Cons other otherLayer tail) reqRL
904+
905+-- | Render a provider's sub-tree (stops at NotProvided or roots).
906+class RenderProviderDeps
907+ (provider :: Symbol) (prefix :: Symbol) (allLayers :: RowList Type) (doc :: Doc)
908+ | provider prefix allLayers -> doc
909+
910+instance RenderProviderDeps NotProvided prefix allLayers (Text "")
911+
912+else instance
913+ ( FindLayerReqs provider allLayers reqRL
914+ , FilterScope reqRL filteredReqRL
915+ , RenderDepTree prefix filteredReqRL allLayers doc
721916 ) =>
722− FormatNode False depth sym reqRL allLayers line
917+ RenderProviderDeps provider prefix allLayers doc
723918
724−-- | Collect provider names for a set of requirements.
725−class ProviderNames (reqRL :: RowList Type) (allLayers :: RowList Type) (out :: Symbol) | reqRL allLayers -> out
919+-- | Render a dependency tree with tree connectors.
920+class RenderDepTree
921+ (prefix :: Symbol) (reqRL :: RowList Type) (allLayers :: RowList Type) (doc :: Doc)
922+ | prefix reqRL allLayers -> doc
726923
727−instance ProviderNames Nil allLayers ""
924+instance RenderDepTree prefix Nil allLayers (Text "")
728925
926+-- Last child: └───
729927else instance
730928 ( FindProvider label allLayers provider
929+ , IsNotProvided provider isNP
930+ , Append prefix "│ " prePrefix
931+ , Append prefix " " contPrefix
932+ , Append prefix "└───" branchPrefix
933+ , RenderDepChildNode isNP provider label ty prePrefix contPrefix branchPrefix allLayers doc
731934 ) =>
732− ProviderNames (Cons label ty Nil) allLayers provider
935+ RenderDepTree prefix (Cons label ty Nil) allLayers doc
733936
937+-- Non-last child: ├───
734938else instance
735939 ( FindProvider label allLayers provider
736− , ProviderNames tail allLayers rest
737− , Append provider ", " s1
738− , Append s1 rest out
940+ , IsNotProvided provider isNP
941+ , Append prefix "│ " contPrefix
942+ , Append prefix "├───" branchPrefix
943+ , RenderDepChildNode isNP provider label ty contPrefix contPrefix branchPrefix allLayers childDoc
944+ , RenderDepTree prefix tail allLayers restDoc
945+ ) =>
946+ RenderDepTree prefix (Cons label ty tail) allLayers (Above childDoc restDoc)
947+
948+-- | Extract the first label from a RowList.
949+class FirstProvLabel (rl :: RowList Type) (label :: Symbol) | rl -> label
950+instance FirstProvLabel (Cons label ty tail) label
951+
952+-- | Dispatch: skip layers without missing deps, render tree for others.
953+class PrintLayerDispatch
954+ (show :: Boolean) (displayName :: Symbol) (filteredReqRL :: RowList Type)
955+ (tail :: RowList Type) (allLayers :: RowList Type) (doc :: Doc)
956+ | show displayName filteredReqRL tail allLayers -> doc
957+
958+instance
959+ PrintEdgesASCII tail allLayers doc =>
960+ PrintLayerDispatch False displayName filteredReqRL tail allLayers doc
961+
962+else instance
963+ ( BoxTop displayName topLine, BoxMid displayName midLine, BoxBot displayName botLine
964+ , RenderDepTree " " filteredReqRL allLayers treeDoc
965+ , PrintEdgesASCII tail allLayers restDoc
966+ ) =>
967+ PrintLayerDispatch True displayName filteredReqRL tail allLayers
968+ (Above (Text topLine) (Above (Text midLine) (Above (Text botLine) (Above treeDoc (Above (Text "") restDoc)))))
969+
970+-- | Walk all layers emitting ASCII dependency trees (for error messages).
971+class PrintLayersASCII (rl :: RowList Type) (doc :: Doc) | rl -> doc
972+class PrintEdgesASCII (rl :: RowList Type) (allLayers :: RowList Type) (doc :: Doc) | rl allLayers -> doc
973+
974+instance PrintLayersASCII Nil (Text "")
975+else instance PrintEdgesASCII rl rl doc => PrintLayersASCII rl doc
976+
977+instance PrintEdgesASCII Nil allLayers (Text "")
978+
979+else instance
980+ ( RowToList req reqRL
981+ , FilterScope reqRL filteredReqRL
982+ , RowToList prov provRL
983+ , FirstProvLabel provRL displayName
984+ , HasDirectMissing filteredReqRL allLayers hasMissing
985+ , PrintLayerDispatch hasMissing displayName filteredReqRL tail allLayers doc
739986 ) =>
740− ProviderNames (Cons label ty tail) allLayers out
987+ PrintEdgesASCII (Cons sym (OmLayer req err (Record prov)) tail) allLayers doc
741988
742989wireLayersDebug
743990 :: forall layers rl req err prov doc
744991 . RowToList layers rl
745992 => PrintLayersRL rl doc
746− => Warn (Above (Text "") (Above (Text "Layer wiring:") doc))
747− => WireLayersRL rl layers (scope :: Scope) () () req err prov
993+ => Warn (Above (Text "") (Above (Text "%%{init: {\"flowchart\": {\"defaultRenderer\": \"elk\"}} }%%") (Above (Text "flowchart LR") doc)))
994+ => TopoWire rl layers (scope :: Scope) () () req err prov
748995 => Record layers
749996 -> OmLayer req err (Record prov)
750997wireLayersDebug = wireLayers
test/Test/Yoga/Om/Layer/InstancesSpec.purs+93
@@ -2,11 +2,14 @@ module Test.Yoga.Om.Layer.InstancesSpec where
22
33import Prelude
44
5+import Control.Alt ((<|>))
6+import Control.Monad.Reader (ask, local)
57import Control.Parallel (parApply)
68import Effect.Class (liftEffect)
79import Effect.Ref as Ref
810import Test.Spec (Spec, describe, it)
911import Test.Spec.Assertions (shouldEqual)
12+import Effect.Exception as Exception
1013import Yoga.Om as Om
1114import Yoga.Om.Layer (OmLayer, makeLayer, runLayer)
1215
@@ -46,6 +49,96 @@ spec = do
4649 result <- Om.runOm {} { exception: \_ -> pure { result: 0 } } (runLayer {} layer)
4750 result.result `shouldEqual` 15
4851
52+ it "runs layers against a superset context" do
53+ let
54+ layer :: OmLayer (a :: Int) () { out :: Int }
55+ layer = makeLayer do
56+ { a } <- Om.ask
57+ pure { out: a + 1 }
58+
59+ ctx = { a: 41, extra: "ok" }
60+
61+ result <- Om.runOm ctx { exception: \_ -> pure { out: 0 } } (runLayer ctx layer)
62+ result.out `shouldEqual` 42
63+
64+ describe "MonadEffect / MonadAff" do
65+
66+ it "liftEffect works directly in layer bind" do
67+ ref <- liftEffect $ Ref.new 0
68+ let
69+ layer :: OmLayer () () Int
70+ layer = do
71+ liftEffect $ Ref.write 42 ref
72+ liftEffect $ Ref.read ref
73+
74+ result <- Om.runOm {} { exception: \_ -> pure 0 } (runLayer {} layer)
75+ result `shouldEqual` 42
76+
77+ describe "MonadAsk / MonadReader" do
78+
79+ it "ask reads the context directly" do
80+ let
81+ layer :: OmLayer (port :: Int) () Int
82+ layer = do
83+ ctx <- ask
84+ pure ctx.port
85+
86+ result <- Om.runOm { port: 8080 } { exception: \_ -> pure 0 } (runLayer { port: 8080 } layer)
87+ result `shouldEqual` 8080
88+
89+ it "local modifies context for a layer" do
90+ let
91+ inner :: OmLayer (port :: Int) () Int
92+ inner = do
93+ ctx <- ask
94+ pure ctx.port
95+
96+ layer :: OmLayer (port :: Int) () Int
97+ layer = local (\r -> r { port = r.port + 1 }) inner
98+
99+ result <- Om.runOm { port: 8080 } { exception: \_ -> pure 0 } (runLayer { port: 8080 } layer)
100+ result `shouldEqual` 8081
101+
102+ describe "MonadThrow / MonadError" do
103+
104+ it "throwError raises into the error channel" do
105+ let
106+ layer :: OmLayer () (myErr :: String) Int
107+ layer = Om.throw { myErr: "boom" }
108+
109+ result <- Om.runOm {}
110+ { exception: \_ -> pure (-1), myErr: \_ -> pure (-2) }
111+ (runLayer {} layer)
112+ result `shouldEqual` (-2)
113+
114+ describe "Alt" do
115+
116+ it "falls back to second layer on failure" do
117+ let
118+ failing :: OmLayer () () Int
119+ failing = Om.throw { exception: Exception.error "nope" }
120+
121+ fallback :: OmLayer () () Int
122+ fallback = pure 99
123+
124+ layer = failing <|> fallback
125+
126+ result <- Om.runOm {} { exception: \_ -> pure (-1) } (runLayer {} layer)
127+ result `shouldEqual` 99
128+
129+ describe "Semigroup" do
130+
131+ it "appends layer outputs" do
132+ let
133+ a :: OmLayer () () String
134+ a = pure "hello"
135+
136+ b :: OmLayer () () String
137+ b = pure " world"
138+
139+ result <- Om.runOm {} { exception: \_ -> pure "" } (runLayer {} (a <> b))
140+ result `shouldEqual` "hello world"
141+
49142 describe "Parallel" do
50143
51144 it "runs layers in parallel via parApply" do
test/Test/Yoga/Om/Layer/PureInferenceSpec.pursnew file+22
@@ -0,0 +1,22 @@
1+module Test.Yoga.Om.Layer.PureInferenceSpec where
2+
3+import Prelude
4+
5+import Test.Spec (Spec, describe, it)
6+import Test.Spec.Assertions (shouldEqual)
7+import Yoga.Om as Om
8+import Yoga.Om.Layer (OmLayer, withValue, makeLayer, runLayer)
9+
10+spec :: Spec Unit
11+spec = describe "withValue" do
12+
13+ it "combines a plain record with a layer without needing VTA" do
14+ let
15+ layer :: OmLayer () () { value :: Int }
16+ layer = makeLayer (pure { value: 42 })
17+
18+ result = layer # withValue { extra: "hello" }
19+
20+ out <- Om.runOm {} { exception: \_ -> pure { value: -1, extra: "" } } (runLayer {} result)
21+ out.value `shouldEqual` 42
22+ out.extra `shouldEqual` "hello"