git.iko.soy/rowtype-yoga/purescript-yoga-om-layer
on main help sign in
v1.2.0: TopoWire error graph and ref-lifted dependency
8a57b3cMark
.spec-results+33−33
@@ -2,231 +2,231 @@
2 [
2 [
3 "Automatic Memoization builds the same layer only once when used in two branches",
3 "Automatic Memoization builds the same layer only once when used in two branches",
4 {
4 {
5− "timestamp": "1771091609999.0",
5+ "timestamp": "1771460758484.0",
6 "success": true
6 "success": true
7 }
7 }
8 ],
8 ],
9 [
9 [
10 "Automatic Memoization memoized bracketLayer acquires and releases only once",
10 "Automatic Memoization memoized bracketLayer acquires and releases only once",
11 {
11 {
12− "timestamp": "1771091609999.0",
12+ "timestamp": "1771460758484.0",
13 "success": true
13 "success": true
14 }
14 }
15 ],
15 ],
16 [
16 [
17 "Automatic Memoization memoized scoped layer runs finalizer only once",
17 "Automatic Memoization memoized scoped layer runs finalizer only once",
18 {
18 {
19− "timestamp": "1771091609999.0",
19+ "timestamp": "1771460758484.0",
20 "success": true
20 "success": true
21 }
21 }
22 ],
22 ],
23 [
23 [
24 "Composition proves Row.Nub deduplicates shared requirements at type level",
24 "Composition proves Row.Nub deduplicates shared requirements at type level",
25 {
25 {
26− "timestamp": "1771091609999.0",
26+ "timestamp": "1771460758484.0",
27 "success": true
27 "success": true
28 }
28 }
29 ],
29 ],
30 [
30 [
31 "Composition proves deduplication at runtime via shared Ref",
31 "Composition proves deduplication at runtime via shared Ref",
32 {
32 {
33− "timestamp": "1771091609999.0",
33+ "timestamp": "1771460758484.0",
34 "success": true
34 "success": true
35 }
35 }
36 ],
36 ],
37 [
37 [
38 "Error Channel composes layers with different error types",
38 "Error Channel composes layers with different error types",
39 {
39 {
40− "timestamp": "1771091609999.0",
40+ "timestamp": "1771460758484.0",
41 "success": true
41 "success": true
42 }
42 }
43 ],
43 ],
44 [
44 [
45 "Error Channel propagates errors from the first layer",
45 "Error Channel propagates errors from the first layer",
46 {
46 {
47− "timestamp": "1771091609999.0",
47+ "timestamp": "1771460758484.0",
48 "success": true
48 "success": true
49 }
49 }
50 ],
50 ],
51 [
51 [
52 "Error Channel propagates errors from the second layer",
52 "Error Channel propagates errors from the second layer",
53 {
53 {
54− "timestamp": "1771091609999.0",
54+ "timestamp": "1771460758484.0",
55 "success": true
55 "success": true
56 }
56 }
57 ],
57 ],
58 [
58 [
59 "Error Channel propagates errors through vertical composition",
59 "Error Channel propagates errors through vertical composition",
60 {
60 {
61− "timestamp": "1771091609999.0",
61+ "timestamp": "1771460758484.0",
62 "success": true
62 "success": true
63 }
63 }
64 ],
64 ],
65 [
65 [
66 "Error Channel scoped layer error triggers finalizers",
66 "Error Channel scoped layer error triggers finalizers",
67 {
67 {
68− "timestamp": "1771091609999.0",
68+ "timestamp": "1771460758484.0",
69 "success": true
69 "success": true
70 }
70 }
71 ],
71 ],
72 [
72 [
73 "Error Channel scoped layers with errors run via runScopedWith",
73 "Error Channel scoped layers with errors run via runScopedWith",
74 {
74 {
75− "timestamp": "1771091609999.0",
75+ "timestamp": "1771460758484.0",
76 "success": true
76 "success": true
77 }
77 }
78 ],
78 ],
79 [
79 [
80 "Functor / Apply / Bind applies two layers",
80 "Functor / Apply / Bind applies two layers",
81 {
81 {
82− "timestamp": "1771091609999.0",
82+ "timestamp": "1771460758484.0",
83 "success": true
83 "success": true
84 }
84 }
85 ],
85 ],
86 [
86 [
87 "Functor / Apply / Bind binds layers sequentially",
87 "Functor / Apply / Bind binds layers sequentially",
88 {
88 {
89− "timestamp": "1771091609999.0",
89+ "timestamp": "1771460758484.0",
90 "success": true
90 "success": true
91 }
91 }
92 ],
92 ],
93 [
93 [
94 "Functor / Apply / Bind maps over layer output",
94 "Functor / Apply / Bind maps over layer output",
95 {
95 {
96− "timestamp": "1771091609999.0",
96+ "timestamp": "1771460758484.0",
97 "success": true
97 "success": true
98 }
98 }
99 ],
99 ],
100 [
100 [
101 "Layer recovering does not retry on non-matching errors",
101 "Layer recovering does not retry on non-matching errors",
102 {
102 {
103− "timestamp": "1771091609999.0",
103+ "timestamp": "1771460758484.0",
104 "success": true
104 "success": true
105 }
105 }
106 ],
106 ],
107 [
107 [
108 "Layer recovering only retries the specified error in a multi-error layer",
108 "Layer recovering only retries the specified error in a multi-error layer",
109 {
109 {
110− "timestamp": "1771091609999.0",
110+ "timestamp": "1771460758484.0",
111 "success": true
111 "success": true
112 }
112 }
113 ],
113 ],
114 [
114 [
115 "Layer recovering preserves the error when retries are exhausted",
115 "Layer recovering preserves the error when retries are exhausted",
116 {
116 {
117− "timestamp": "1771091609999.0",
117+ "timestamp": "1771460758484.0",
118 "success": true
118 "success": true
119 }
119 }
120 ],
120 ],
121 [
121 [
122 "Layer recovering retries layer construction on matching errors",
122 "Layer recovering retries layer construction on matching errors",
123 {
123 {
124− "timestamp": "1771091609999.0",
124+ "timestamp": "1771460758484.0",
125 "success": true
125 "success": true
126 }
126 }
127 ],
127 ],
128 [
128 [
129 "Layer recovering uses RetryStatus in the check",
129 "Layer recovering uses RetryStatus in the check",
130 {
130 {
131− "timestamp": "1771091609999.0",
131+ "timestamp": "1771460758484.0",
132 "success": true
132 "success": true
133 }
133 }
134 ],
134 ],
135 [
135 [
136 "Layer repeating repeats layer construction while condition holds",
136 "Layer repeating repeats layer construction while condition holds",
137 {
137 {
138− "timestamp": "1771091609999.0",
138+ "timestamp": "1771460758484.0",
139 "success": true
139 "success": true
140 }
140 }
141 ],
141 ],
142 [
142 [
143 "Layer repeating stops when policy exhausted",
143 "Layer repeating stops when policy exhausted",
144 {
144 {
145− "timestamp": "1771091609999.0",
145+ "timestamp": "1771460758484.0",
146 "success": true
146 "success": true
147 }
147 }
148 ],
148 ],
149 [
149 [
150 "Parallel runs layers in parallel via parApply",
150 "Parallel runs layers in parallel via parApply",
151 {
151 {
152− "timestamp": "1771091609999.0",
152+ "timestamp": "1771460758484.0",
153 "success": true
153 "success": true
154 }
154 }
155 ],
155 ],
156 [
156 [
157 "Scoped Layers combines scoped and non-scoped layers",
157 "Scoped Layers combines scoped and non-scoped layers",
158 {
158 {
159− "timestamp": "1771091609999.0",
159+ "timestamp": "1771460758484.0",
160 "success": true
160 "success": true
161 }
161 }
162 ],
162 ],
163 [
163 [
164 "Scoped Layers fresh layer builds twice even when same base layer",
164 "Scoped Layers fresh layer builds twice even when same base layer",
165 {
165 {
166− "timestamp": "1771091609999.0",
166+ "timestamp": "1771460758484.0",
167 "success": true
167 "success": true
168 }
168 }
169 ],
169 ],
170 [
170 [
171 "Scoped Layers runs finalizers in reverse order on success",
171 "Scoped Layers runs finalizers in reverse order on success",
172 {
172 {
173− "timestamp": "1771091609999.0",
173+ "timestamp": "1771460758484.0",
174 "success": true
174 "success": true
175 }
175 }
176 ],
176 ],
177 [
177 [
178 "Scoped Layers runs finalizers when the callback throws",
178 "Scoped Layers runs finalizers when the callback throws",
179 {
179 {
180− "timestamp": "1771091609999.0",
180+ "timestamp": "1771460758484.0",
181 "success": true
181 "success": true
182 }
182 }
183 ],
183 ],
184 [
184 [
185 "Scoped Layers threads finalizers through vertical composition (provide)",
185 "Scoped Layers threads finalizers through vertical composition (provide)",
186 {
186 {
187− "timestamp": "1771091609999.0",
187+ "timestamp": "1771460758484.0",
188 "success": true
188 "success": true
189 }
189 }
190 ],
190 ],
191 [
191 [
192 "Scoped Layers works with bracketLayer",
192 "Scoped Layers works with bracketLayer",
193 {
193 {
194− "timestamp": "1771091609999.0",
194+ "timestamp": "1771460758484.0",
195 "success": true
195 "success": true
196 }
196 }
197 ],
197 ],
198 [
198 [
199 "wireLayers memoizes shared dependencies within wireLayers",
199 "wireLayers memoizes shared dependencies within wireLayers",
200 {
200 {
201− "timestamp": "1771091609999.0",
201+ "timestamp": "1771460758484.0",
202 "success": true
202 "success": true
203 }
203 }
204 ],
204 ],
205 [
205 [
206 "wireLayers wireLayersDebug emits dependency graph as compiler warning",
206 "wireLayers wireLayersDebug emits dependency graph as compiler warning",
207 {
207 {
208− "timestamp": "1771091609999.0",
208+ "timestamp": "1771460758484.0",
209 "success": true
209 "success": true
210 }
210 }
211 ],
211 ],
212 [
212 [
213 "wireLayers wires a diamond dependency",
213 "wireLayers wires a diamond dependency",
214 {
214 {
215− "timestamp": "1771091609999.0",
215+ "timestamp": "1771460758484.0",
216 "success": true
216 "success": true
217 }
217 }
218 ],
218 ],
219 [
219 [
220 "wireLayers wires a simple linear chain (A -> B -> C)",
220 "wireLayers wires a simple linear chain (A -> B -> C)",
221 {
221 {
222− "timestamp": "1771091609999.0",
222+ "timestamp": "1771460758484.0",
223 "success": true
223 "success": true
224 }
224 }
225 ],
225 ],
226 [
226 [
227 "wireLayers wires independent layers with no inter-dependencies",
227 "wireLayers wires independent layers with no inter-dependencies",
228 {
228 {
229− "timestamp": "1771091609999.0",
229+ "timestamp": "1771460758484.0",
230 "success": true
230 "success": true
231 }
231 }
232 ]
232 ]
compile-fail-tests/WireLayersMissingDep.purs+4−4
@@ -1,4 +1,4 @@
1−-- EXPECT: TypesDoNotUnify
1+-- EXPECT: Dependency not provided!
2module Yoga.Om.Layer.CompileFailTest where
2module Yoga.Om.Layer.CompileFailTest where
3
3
4import Prelude
4import Prelude
@@ -8,9 +8,9 @@ import Yoga.Om.Layer (OmLayer, Scope, makeLayer, wireLayers)
8a_base :: OmLayer (scope :: Scope) () { base :: String }
8a_base :: OmLayer (scope :: Scope) () { base :: String }
9a_base = makeLayer (pure { base: "ok" })
9a_base = makeLayer (pure { base: "ok" })
10
10
11−b_upper :: OmLayer (scope :: Scope, missing :: Int) () { upper :: String }
11+b_upper :: OmLayer (scope :: Scope, port :: Int) () { upper :: String }
12b_upper = makeLayer do
12b_upper = makeLayer do
13− { missing } <- Om.ask
13+ { port } <- Om.ask
14− pure { upper: show missing }
14+ pure { upper: show port }
15
15
16bad = wireLayers { a_base, b_upper }
16bad = 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+20−1
@@ -44,6 +44,9 @@
44 {
44 {
45 "record-studio": ">=1.0.4 <2.0.0"
45 "record-studio": ">=1.0.4 <2.0.0"
46 },
46 },
47+ {
48+ "ref-lifted": ">=0.0.1 <1.0.0"
49+ },
47 {
50 {
48 "refs": ">=6.0.0 <7.0.0"
51 "refs": ">=6.0.0 <7.0.0"
49 },
52 },
@@ -53,6 +56,9 @@
53 {
56 {
54 "type-equality": ">=4.0.1 <5.0.0"
57 "type-equality": ">=4.0.1 <5.0.0"
55 },
58 },
59+ {
60+ "typelevel-prelude": ">=7.0.0 <8.0.0"
61+ },
56 {
62 {
57 "unsafe-coerce": ">=6.0.0 <7.0.0"
63 "unsafe-coerce": ">=6.0.0 <7.0.0"
58 },
64 },
@@ -676,7 +682,11 @@
676 "zipperarray": "2.0.0"
682 "zipperarray": "2.0.0"
677 }
683 }
678 },
684 },
679− "extra_packages": {}
685+ "extra_packages": {
686+ "ref-lifted": {
687+ "path": "../purescript-ref-lifted"
688+ }
689+ }
680 },
690 },
681 "packages": {
691 "packages": {
682 "aff": {
692 "aff": {
@@ -1567,6 +1577,15 @@
1567 "unsafe-coerce"
1577 "unsafe-coerce"
1568 ]
1578 ]
1569 },
1579 },
1580+ "ref-lifted": {
1581+ "type": "local",
1582+ "path": "../purescript-ref-lifted",
1583+ "dependencies": [
1584+ "effect",
1585+ "prelude",
1586+ "refs"
1587+ ]
1588+ },
1570 "refs": {
1589 "refs": {
1571 "type": "registry",
1590 "type": "registry",
1572 "version": "6.0.0",
1591 "version": "6.0.0",
spago.yaml+6−1
@@ -1,11 +1,14 @@
1workspace:
1workspace:
2 packageSet:
2 packageSet:
3 registry: 73.0.0
3 registry: 73.0.0
4+ extraPackages:
5+ ref-lifted:
6+ path: ../purescript-ref-lifted
4
7
5package:
8package:
6 name: yoga-om-layer
9 name: yoga-om-layer
7 publish:
10 publish:
8− version: 1.1.1
11+ version: 1.2.0
9 license: MIT
12 license: MIT
10 location:
13 location:
11 githubOwner: rowtype-yoga
14 githubOwner: rowtype-yoga
@@ -24,9 +27,11 @@ package:
24 - prelude: ">=6.0.2 <7.0.0"
27 - prelude: ">=6.0.2 <7.0.0"
25 - record: ">=4.0.0 <5.0.0"
28 - record: ">=4.0.0 <5.0.0"
26 - record-studio: ">=1.0.4 <2.0.0"
29 - record-studio: ">=1.0.4 <2.0.0"
30+ - ref-lifted: ">=0.0.1 <1.0.0"
27 - refs: ">=6.0.0 <7.0.0"
31 - refs: ">=6.0.0 <7.0.0"
28 - transformers: ">=6.1.0 <7.0.0"
32 - transformers: ">=6.1.0 <7.0.0"
29 - type-equality: ">=4.0.1 <5.0.0"
33 - type-equality: ">=4.0.1 <5.0.0"
34+ - typelevel-prelude: ">=7.0.0 <8.0.0"
30 - unsafe-coerce: ">=6.0.0 <7.0.0"
35 - unsafe-coerce: ">=6.0.0 <7.0.0"
31 - variant: ">=8.0.0 <9.0.0"
36 - variant: ">=8.0.0 <9.0.0"
32 - yoga-om: ">=1.4.0 <2.0.0"
37 - yoga-om: ">=1.4.0 <2.0.0"
src/Yoga/Om/Layer.purs+328−29
@@ -13,6 +13,7 @@ module Yoga.Om.Layer
13 , runScopedWith
13 , runScopedWith
14 , withScoped
14 , withScoped
15 , withScopedWith
15 , withScopedWith
16+ , scoped
16 , combineRequirements
17 , combineRequirements
17 , provide
18 , provide
18 , (>->)
19 , (>->)
@@ -20,18 +21,33 @@ module Yoga.Om.Layer
20 , repeating
21 , repeating
21 , wireLayers
22 , wireLayers
22 , wireLayersDebug
23 , wireLayersDebug
23− , wireLayersRL
24− , class WireLayersRL
25 , class FilterScope
24 , class FilterScope
26 , class FindProvider
25 , class FindProvider
27 , class FindProviderMatch
26 , class FindProviderMatch
28 , class HasLabel
27 , class HasLabel
29 , class EmitEdges
28 , class EmitEdges
29+ , class DashesFor
30+ , class BoxTop, class BoxMid, class BoxBot
31+ , class RenderDepChildNode
32+ , class IsNotProvided
33+ , class HasDirectMissing, class HasDirectMissingOr
34+ , class FindLayerReqs
35+ , class RenderProviderDeps
36+ , class RenderDepTree
37+ , class FirstProvLabel
38+ , class PrintLayerDispatch
30 , class PrintLayersRL
39 , class PrintLayersRL
31 , class PrintEdgesRL
40 , class PrintEdgesRL
41+ , class PrintLayersASCII
42+ , class PrintEdgesASCII
43+ , NotProvided
32 , class CheckAllProvided
44 , class CheckAllProvided
33 , class CheckAllLabelsExist
45 , class CheckAllLabelsExist
34 , class CheckLabelExists
46 , class CheckLabelExists
47+ , class TopoWire, topoWire
48+ , class AllLabelsIn
49+ , class FindReady
50+ , class FindReadyDispatch
35 ) where
51 ) where
36
52
37import Prelude
53import Prelude
@@ -60,8 +76,10 @@ import Prim.Row as Row
60import Type.Proxy (Proxy(..))
76import Type.Proxy (Proxy(..))
61import Prim.RowList (class RowToList, Cons, Nil, RowList)
77import Prim.RowList (class RowToList, Cons, Nil, RowList)
62import Prim.Boolean (True, False)
78import Prim.Boolean (True, False)
63−import Prim.Symbol (class Append)
79+import Prim.Symbol (class Append, class Cons)
80+import Type.Data.Boolean (class And)
81+import Type.RowList (class RowListAppend)
64−import Prim.TypeError (class Fail, class Warn, Above, Doc, Quote, Text)
82+import Prim.TypeError (class Fail, class Warn, Above, Beside, Doc, Quote, Text)
65import Record as Record
83import Record as Record
66import Record.Studio (class Keys)
84import Record.Studio (class Keys)
67import Type.Equality (class TypeEquals)
85import Type.Equality (class TypeEquals)
@@ -352,7 +370,7 @@ withScoped
352 . OmLayer (scope :: Scope) () (Record prov)
370 . OmLayer (scope :: Scope) () (Record prov)
353 -> (Record prov -> Aff a)
371 -> (Record prov -> Aff a)
354 -> Aff a
372 -> Aff a
355−withScoped = withScopedWith { exception: \_ -> pure (unsafeCoerce {}) }
373+withScoped = withScopedWith { exception: throwError }
356
374
357-- | Like `runScoped` but accepts error handlers for layers with typed errors.
375-- | Like `runScoped` but accepts error handlers for layers with typed errors.
358runScopedWith
376runScopedWith
@@ -385,6 +403,17 @@ withScopedWith handlers layer callback = bracket acquire release use
385 release { scope } = closeScope scope
403 release { scope } = closeScope scope
386 use { prov } = callback prov
404 use { prov } = callback prov
387
405
406+scoped
407+ :: forall prov a r rl err_ err
408+ . RowToList (exception :: Error -> Aff (Record prov) | r) rl
409+ => VariantMatchCases rl err_ (Aff (Record prov))
410+ => Union err_ () (exception :: Error | err)
411+ => { exception :: Error -> Aff (Record prov) | r }
412+ -> OmLayer (scope :: Scope) err (Record prov)
413+ -> (Record prov -> Aff a)
414+ -> Aff a
415+scoped = withScopedWith
416+
388-- =============================================================================
417-- =============================================================================
389-- Horizontal composition — combine two layers with automatic deduplication
418-- Horizontal composition — combine two layers with automatic deduplication
390-- =============================================================================
419-- =============================================================================
@@ -481,13 +510,82 @@ repeating policy shouldRepeat (OmLayer layerId om) =
481 (applyAndDelay policy status >>= maybe (pure result) go)
510 (applyAndDelay policy status >>= maybe (pure result) go)
482 (pure result)
511 (pure result)
483
512
513+
484-- =============================================================================
514-- =============================================================================
485−-- Auto-wiring
515+-- Topological auto-wiring (order-independent)
486-- =============================================================================
516-- =============================================================================
487
517
488−class
518+-- | Check if ALL labels in `needles` exist in `haystack`, returning a Boolean.
489− WireLayersRL
519+class AllLabelsIn (needles :: RowList Type) (haystack :: RowList Type) (result :: Boolean)
520+ | needles haystack -> result
521+
522+instance AllLabelsIn Nil haystack True
523+
524+instance
525+ ( HasLabel label haystack found
526+ , AllLabelsIn tail haystack tailResult
527+ , And found tailResult result
528+ ) =>
529+ AllLabelsIn (Cons label ty tail) haystack result
530+
531+-- | Dispatch based on whether a layer is ready (deps satisfied).
532+class FindReadyDispatch
533+ (ready :: Boolean)
534+ (sym :: Symbol)
535+ (layer :: Type)
536+ (tail :: RowList Type)
537+ (available :: RowList Type)
538+ (allLayersRL :: RowList Type)
539+ (foundSym :: Symbol)
540+ (foundLayer :: Type)
541+ (rest :: RowList Type)
542+ | ready sym layer tail available allLayersRL -> foundSym foundLayer rest
543+
544+instance FindReadyDispatch True sym layer tail available allLayersRL sym layer tail
545+
546+else instance
547+ FindReady tail available allLayersRL foundSym foundLayer rest' =>
548+ FindReadyDispatch False sym layer tail available allLayersRL foundSym foundLayer (Cons sym layer rest')
549+
550+-- | Scan candidates for the first layer whose requirements are satisfied.
551+class FindReady
552+ (candidates :: RowList Type)
553+ (available :: RowList Type)
554+ (allLayersRL :: RowList Type)
555+ (sym :: Symbol)
556+ (layer :: Type)
557+ (rest :: RowList Type)
558+ | candidates available allLayersRL -> sym layer rest
559+
560+instance
561+ ( PrintLayersASCII allLayersRL graphDoc
562+ , Fail
563+ ( Above (Text "")
564+ ( Above (Text "Dependency not provided!")
565+ ( Above (Text "")
566+ ( Above (Text "No remaining layer has all its dependencies satisfied.")
567+ ( Above (Text "")
568+ ( Above (Text "Dependency graph:") graphDoc
569+ )
570+ )
571+ )
572+ )
573+ )
574+ )
575+ ) =>
576+ FindReady Nil available allLayersRL sym layer rest
577+
578+else instance
579+ ( RowToList req reqRL
580+ , AllLabelsIn reqRL available isReady
581+ , FindReadyDispatch isReady sym (OmLayer req err (Record prov)) tail available allLayersRL foundSym foundLayer rest
582+ ) =>
583+ FindReady (Cons sym (OmLayer req err (Record prov)) tail) available allLayersRL foundSym foundLayer rest
584+
585+-- | Topologically sort and wire layers. Each iteration finds a layer whose
586+-- | dependencies are already satisfied, processes it, and adds its provisions.
587+class TopoWire
490− (rl :: RowList Type)
588+ (remaining :: RowList Type)
491 (layers :: Row Type)
589 (layers :: Row Type)
492 (accReq :: Row Type)
590 (accReq :: Row Type)
493 (accErr :: Row Type)
591 (accErr :: Row Type)
@@ -495,20 +593,25 @@ class
495 (resReq :: Row Type)
593 (resReq :: Row Type)
496 (resErr :: Row Type)
594 (resErr :: Row Type)
497 (resProv :: Row Type)
595 (resProv :: Row Type)
498− | rl layers accReq accErr accProv -> resReq resErr resProv
596+ | remaining layers accReq accErr accProv -> resReq resErr resProv
499 where
597 where
500− wireLayersRL
598+ topoWire
501− :: Proxy rl
599+ :: Proxy remaining
502 -> Record layers
600 -> Record layers
503 -> OmLayer accReq accErr (Record accProv)
601 -> OmLayer accReq accErr (Record accProv)
504 -> OmLayer resReq resErr (Record resProv)
602 -> OmLayer resReq resErr (Record resProv)
505
603
506−instance WireLayersRL Nil layers accReq accErr accProv accReq accErr accProv where
604+instance TopoWire Nil layers accReq accErr accProv accReq accErr accProv where
507− wireLayersRL _ _ acc = acc
605+ topoWire _ _ acc = acc
508
606
509−instance
607+else instance
608+ ( RowToList layers allLayersRL
609+ , RowToList accProv accProvRL
610+ , RowToList accReq accReqRL
611+ , RowListAppend accProvRL accReqRL availableRL
612+ , FindReady remaining availableRL allLayersRL readySym (OmLayer nextReq nextErr (Record nextProv)) rest
510− ( IsSymbol sym
613+ , IsSymbol readySym
511− , Row.Cons sym (OmLayer nextReq nextErr (Record nextProv)) _rest layers
614+ , Row.Cons readySym (OmLayer nextReq nextErr (Record nextProv)) _rest layers
512 -- next's requirements must be subset of (accProv + accReq)
615 -- next's requirements must be subset of (accProv + accReq)
513 , Union accProv accReq provReqOut
616 , Union accProv accReq provReqOut
514 , Nub provReqOut provReqOut
617 , Nub provReqOut provReqOut
@@ -521,19 +624,18 @@ instance
521 , Nub errMergedRaw errMerged
624 , Nub errMergedRaw errMerged
522 , Union accErr _e1 errMerged
625 , Union accErr _e1 errMerged
523 , Union nextErr _e2 errMerged
626 , Union nextErr _e2 errMerged
524− -- merge provisions (Nub provMerged provMerged ensures disjoint)
627+ -- merge provisions
525 , Union accProv nextProv provMerged
628 , Union accProv nextProv provMerged
526 , Nub provMerged provMerged
629 , Nub provMerged provMerged
527 -- recurse
630 -- recurse
528− , WireLayersRL tail layers accReq errMerged provMerged resReq resErr resProv
631+ , TopoWire rest layers accReq errMerged provMerged resReq resErr resProv
529 ) =>
632 ) =>
530− WireLayersRL (Cons sym (OmLayer nextReq nextErr (Record nextProv)) tail) layers accReq accErr accProv resReq resErr resProv
633+ TopoWire remaining layers accReq accErr accProv resReq resErr resProv where
531− where
532− wireLayersRL _ layers acc =
634+ topoWire _ layers acc =
533− wireLayersRL (Proxy :: Proxy tail) layers combined
635+ topoWire (Proxy :: Proxy rest) layers combined
534 where
636 where
535 next :: OmLayer nextReq nextErr (Record nextProv)
637 next :: OmLayer nextReq nextErr (Record nextProv)
536− next = Record.get (Proxy :: Proxy sym) layers
638+ next = Record.get (Proxy :: Proxy readySym) layers
537
639
538 combined :: OmLayer accReq errMerged (Record provMerged)
640 combined :: OmLayer accReq errMerged (Record provMerged)
539 combined = OmLayer (freshId unit) do
641 combined = OmLayer (freshId unit) do
@@ -544,11 +646,11 @@ instance
544wireLayers
646wireLayers
545 :: forall layers rl req err prov
647 :: forall layers rl req err prov
546 . RowToList layers rl
648 . RowToList layers rl
547− => WireLayersRL rl layers (scope :: Scope) () () req err prov
649+ => TopoWire rl layers (scope :: Scope) () () req err prov
548 => Record layers
650 => Record layers
549 -> OmLayer req err (Record prov)
651 -> OmLayer req err (Record prov)
550wireLayers layers =
652wireLayers layers =
551− wireLayersRL (Proxy :: Proxy rl) layers seed
653+ topoWire (Proxy :: Proxy rl) layers seed
552 where
654 where
553 seed = pure {} :: OmLayer (scope :: Scope) () (Record ())
655 seed = pure {} :: OmLayer (scope :: Scope) () (Record ())
554
656
@@ -563,10 +665,16 @@ instance FilterScope Nil Nil
563instance FilterScope (Cons "scope" Scope tail) tail
665instance FilterScope (Cons "scope" Scope tail) tail
564else instance FilterScope tail out => FilterScope (Cons sym ty tail) (Cons sym ty out)
666else instance FilterScope tail out => FilterScope (Cons sym ty tail) (Cons sym ty out)
565
667
668+-- | Marker symbol for unresolved dependencies.
669+type NotProvided :: Symbol
670+type NotProvided = "???"
671+
566-- | Given a label, find which layer in the RowList provides it.
672-- | Given a label, find which layer in the RowList provides it.
567class FindProvider (label :: Symbol) (rl :: RowList Type) (provider :: Symbol) | label rl -> provider
673class FindProvider (label :: Symbol) (rl :: RowList Type) (provider :: Symbol) | label rl -> provider
568
674
569−instance
675+instance FindProvider label Nil NotProvided
676+
677+else instance
570 ( RowToList prov provRL
678 ( RowToList prov provRL
571 , HasLabel label provRL hasIt
679 , HasLabel label provRL hasIt
572 , FindProviderMatch hasIt label sym (Cons sym (OmLayer req err (Record prov)) tail) provider
680 , FindProviderMatch hasIt label sym (Cons sym (OmLayer req err (Record prov)) tail) provider
@@ -603,7 +711,7 @@ else instance
603 ) =>
711 ) =>
604 EmitEdges consumer (Cons label ty tail) allLayers (Above (Text line) restDoc)
712 EmitEdges consumer (Cons label ty tail) allLayers (Above (Text line) restDoc)
605
713
606−-- | Walk all layers emitting D2 edges.
714+-- | Walk all layers emitting D2 edges (mermaid format for wireLayersDebug).
607class PrintLayersRL (rl :: RowList Type) (doc :: Doc) | rl -> doc
715class PrintLayersRL (rl :: RowList Type) (doc :: Doc) | rl -> doc
608class PrintEdgesRL (rl :: RowList Type) (allLayers :: RowList Type) (doc :: Doc) | rl allLayers -> doc
716class PrintEdgesRL (rl :: RowList Type) (allLayers :: RowList Type) (doc :: Doc) | rl allLayers -> doc
609
717
@@ -620,12 +728,203 @@ else instance
620 ) =>
728 ) =>
621 PrintEdgesRL (Cons sym (OmLayer req err (Record prov)) tail) allLayers (Above edgeDoc restDoc)
729 PrintEdgesRL (Cons sym (OmLayer req err (Record prov)) tail) allLayers (Above edgeDoc restDoc)
622
730
731+-- | Map a symbol to same-length dashes (for box borders).
732+class DashesFor (sym :: Symbol) (dashes :: Symbol) | sym -> dashes
733+
734+instance DashesFor "" ""
735+
736+else instance
737+ ( Cons head tail sym
738+ , DashesFor tail rest
739+ , Append "─" rest dashes
740+ ) =>
741+ DashesFor sym dashes
742+
743+-- | Build box parts from a symbol.
744+class BoxTop (sym :: Symbol) (result :: Symbol) | sym -> result
745+
746+instance
747+ ( DashesFor sym dashes
748+ , Append "╭─" dashes s0
749+ , Append s0 "─╮" result
750+ ) =>
751+ BoxTop sym result
752+
753+class BoxMid (sym :: Symbol) (result :: Symbol) | sym -> result
754+
755+instance
756+ ( Append "│ " sym s0
757+ , Append s0 " │" result
758+ ) =>
759+ BoxMid sym result
760+
761+class BoxBot (sym :: Symbol) (result :: Symbol) | sym -> result
762+
763+instance
764+ ( DashesFor sym dashes
765+ , Append "╰─" dashes s0
766+ , Append s0 "─╯" result
767+ ) =>
768+ BoxBot sym result
769+
770+-- | Render a single dependency child node (dispatches on provided vs missing).
771+class RenderDepChildNode
772+ (isNotProvided :: Boolean) (provider :: Symbol) (label :: Symbol) (ty :: Type)
773+ (prePrefix :: Symbol) (contPrefix :: Symbol) (branchPrefix :: Symbol)
774+ (allLayers :: RowList Type) (doc :: Doc)
775+ | isNotProvided provider label ty prePrefix contPrefix branchPrefix allLayers -> doc
776+
777+-- Missing dep: boxed label, type outside
778+instance
779+ ( BoxTop label topLine, BoxMid label midLine, BoxBot label botLine
780+ , Append prePrefix topLine topFull
781+ , Append branchPrefix midLine midFull
782+ , Append contPrefix botLine botFull
783+ ) =>
784+ RenderDepChildNode True provider label ty prePrefix contPrefix branchPrefix allLayers
785+ (Above (Text topFull) (Above (Beside (Text midFull) (Beside (Text " :: ") (Beside (Quote ty) (Text " <-- ⚠️ not provided")))) (Text botFull)))
786+
787+-- Provided dep: boxed label, then recurse into provider's sub-tree
788+instance
789+ ( BoxTop label topLine, BoxMid label midLine, BoxBot label botLine
790+ , Append prePrefix topLine topFull
791+ , Append branchPrefix midLine midFull
792+ , Append contPrefix botLine botFull
793+ , Append contPrefix " " subTreePrefix
794+ , RenderProviderDeps provider subTreePrefix allLayers subDoc
795+ ) =>
796+ RenderDepChildNode False provider label ty prePrefix contPrefix branchPrefix allLayers
797+ (Above (Text topFull) (Above (Text midFull) (Above (Text botFull) subDoc)))
798+
799+-- | Is this provider the NotProvided marker?
800+class IsNotProvided (provider :: Symbol) (result :: Boolean) | provider -> result
801+instance IsNotProvided NotProvided True
802+else instance IsNotProvided other False
803+
804+-- | Does any requirement in this RowList resolve to NotProvided?
805+class HasDirectMissing (reqRL :: RowList Type) (allLayers :: RowList Type) (result :: Boolean)
806+ | reqRL allLayers -> result
807+
808+instance HasDirectMissing Nil allLayers False
809+
810+else instance
811+ ( FindProvider label allLayers provider
812+ , IsNotProvided provider isMissing
813+ , HasDirectMissingOr isMissing tail allLayers result
814+ ) =>
815+ HasDirectMissing (Cons label ty tail) allLayers result
816+
817+class HasDirectMissingOr (found :: Boolean) (tail :: RowList Type) (allLayers :: RowList Type) (result :: Boolean)
818+ | found tail allLayers -> result
819+
820+instance HasDirectMissingOr True tail allLayers True
821+
822+else instance
823+ HasDirectMissing tail allLayers result =>
824+ HasDirectMissingOr False tail allLayers result
825+
826+-- | Look up a layer's requirements by name.
827+class FindLayerReqs (sym :: Symbol) (allLayers :: RowList Type) (reqRL :: RowList Type)
828+ | sym allLayers -> reqRL
829+
830+instance
831+ RowToList req reqRL =>
832+ FindLayerReqs sym (Cons sym (OmLayer req err (Record prov)) tail) reqRL
833+
834+else instance
835+ FindLayerReqs sym tail reqRL =>
836+ FindLayerReqs sym (Cons other otherLayer tail) reqRL
837+
838+-- | Render a provider's sub-tree (stops at NotProvided or roots).
839+class RenderProviderDeps
840+ (provider :: Symbol) (prefix :: Symbol) (allLayers :: RowList Type) (doc :: Doc)
841+ | provider prefix allLayers -> doc
842+
843+instance RenderProviderDeps NotProvided prefix allLayers (Text "")
844+
845+else instance
846+ ( FindLayerReqs provider allLayers reqRL
847+ , FilterScope reqRL filteredReqRL
848+ , RenderDepTree prefix filteredReqRL allLayers doc
849+ ) =>
850+ RenderProviderDeps provider prefix allLayers doc
851+
852+-- | Render a dependency tree with tree connectors.
853+class RenderDepTree
854+ (prefix :: Symbol) (reqRL :: RowList Type) (allLayers :: RowList Type) (doc :: Doc)
855+ | prefix reqRL allLayers -> doc
856+
857+instance RenderDepTree prefix Nil allLayers (Text "")
858+
859+-- Last child: └───
860+else instance
861+ ( FindProvider label allLayers provider
862+ , IsNotProvided provider isNP
863+ , Append prefix "│ " prePrefix
864+ , Append prefix " " contPrefix
865+ , Append prefix "└───" branchPrefix
866+ , RenderDepChildNode isNP provider label ty prePrefix contPrefix branchPrefix allLayers doc
867+ ) =>
868+ RenderDepTree prefix (Cons label ty Nil) allLayers doc
869+
870+-- Non-last child: ├───
871+else instance
872+ ( FindProvider label allLayers provider
873+ , IsNotProvided provider isNP
874+ , Append prefix "│ " contPrefix
875+ , Append prefix "├───" branchPrefix
876+ , RenderDepChildNode isNP provider label ty contPrefix contPrefix branchPrefix allLayers childDoc
877+ , RenderDepTree prefix tail allLayers restDoc
878+ ) =>
879+ RenderDepTree prefix (Cons label ty tail) allLayers (Above childDoc restDoc)
880+
881+-- | Extract the first label from a RowList.
882+class FirstProvLabel (rl :: RowList Type) (label :: Symbol) | rl -> label
883+instance FirstProvLabel (Cons label ty tail) label
884+
885+-- | Dispatch: skip layers without missing deps, render tree for others.
886+class PrintLayerDispatch
887+ (show :: Boolean) (displayName :: Symbol) (filteredReqRL :: RowList Type)
888+ (tail :: RowList Type) (allLayers :: RowList Type) (doc :: Doc)
889+ | show displayName filteredReqRL tail allLayers -> doc
890+
891+instance
892+ PrintEdgesASCII tail allLayers doc =>
893+ PrintLayerDispatch False displayName filteredReqRL tail allLayers doc
894+
895+else instance
896+ ( BoxTop displayName topLine, BoxMid displayName midLine, BoxBot displayName botLine
897+ , RenderDepTree " " filteredReqRL allLayers treeDoc
898+ , PrintEdgesASCII tail allLayers restDoc
899+ ) =>
900+ PrintLayerDispatch True displayName filteredReqRL tail allLayers
901+ (Above (Text topLine) (Above (Text midLine) (Above (Text botLine) (Above treeDoc (Above (Text "") restDoc)))))
902+
903+-- | Walk all layers emitting ASCII dependency trees (for error messages).
904+class PrintLayersASCII (rl :: RowList Type) (doc :: Doc) | rl -> doc
905+class PrintEdgesASCII (rl :: RowList Type) (allLayers :: RowList Type) (doc :: Doc) | rl allLayers -> doc
906+
907+instance PrintLayersASCII Nil (Text "")
908+else instance PrintEdgesASCII rl rl doc => PrintLayersASCII rl doc
909+
910+instance PrintEdgesASCII Nil allLayers (Text "")
911+
912+else instance
913+ ( RowToList req reqRL
914+ , FilterScope reqRL filteredReqRL
915+ , RowToList prov provRL
916+ , FirstProvLabel provRL displayName
917+ , HasDirectMissing filteredReqRL allLayers hasMissing
918+ , PrintLayerDispatch hasMissing displayName filteredReqRL tail allLayers doc
919+ ) =>
920+ PrintEdgesASCII (Cons sym (OmLayer req err (Record prov)) tail) allLayers doc
921+
623wireLayersDebug
922wireLayersDebug
624 :: forall layers rl req err prov doc
923 :: forall layers rl req err prov doc
625 . RowToList layers rl
924 . RowToList layers rl
626 => PrintLayersRL rl doc
925 => PrintLayersRL rl doc
627 => Warn (Above (Text "") (Above (Text "%%{init: {\"flowchart\": {\"defaultRenderer\": \"elk\"}} }%%") (Above (Text "flowchart LR") doc)))
926 => Warn (Above (Text "") (Above (Text "%%{init: {\"flowchart\": {\"defaultRenderer\": \"elk\"}} }%%") (Above (Text "flowchart LR") doc)))
628− => WireLayersRL rl layers (scope :: Scope) () () req err prov
927+ => TopoWire rl layers (scope :: Scope) () () req err prov
629 => Record layers
928 => Record layers
630 -> OmLayer req err (Record prov)
929 -> OmLayer req err (Record prov)
631wireLayersDebug = wireLayers
930wireLayersDebug = wireLayers